From b23c9c45f0bceef4d142c9caa0b08f2e5e098fcc Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 15 Nov 2024 14:10:49 +0100 Subject: [PATCH] Initial + Added skill db read + converter skill_id > readable --- CMakeLists.txt | 2 + conf/char_athena.conf | 4 +- conf/inter_athena.conf | 18 + conf/login_athena.conf | 2 +- sql-files/bot_accounts.sql | 11 + sql-files/main.sql | 4 + src/bot/Brokk/Brokk.vcxproj | 13 +- src/bot/Brokk/Brokk.vcxproj.filters | 33 + src/bot/Brokk/account.cpp | 954 + src/bot/Brokk/account.hpp | 149 + src/bot/Brokk/battle.cpp | 11845 ++++++++++ src/bot/Brokk/battle.hpp | 787 + src/bot/Brokk/bot_account.cpp | 825 + src/bot/Brokk/bot_account.hpp | 133 + src/bot/Brokk/brokk.cpp | 959 +- src/bot/Brokk/brokk.hpp | 24 +- src/bot/Brokk/char_creation_helpers.cpp | 1426 ++ src/bot/Brokk/char_creation_helpers.hpp | 369 + src/bot/Brokk/charakters.cpp | 1107 + src/bot/Brokk/charakters.hpp | 216 + src/bot/Brokk/config_helpers.cpp | 825 + src/bot/Brokk/config_helpers.hpp | 676 + src/bot/Brokk/helper_skills.cpp | 2425 ++ src/bot/Brokk/helper_skills.hpp | 1950 ++ src/bot/Brokk/json.hpp | 24766 +++++++++++++++++++++ src/bot/Brokk/template_conf_bot_acc.json | 44 + src/bot/Sindri/sindri.cpp | 104 + src/bot/Sindri/sindri.hpp | 18 + src/common/core.cpp | 2 +- src/common/core.hpp | 4 +- src/map/pc.cpp | 2 + 31 files changed, 49685 insertions(+), 12 deletions(-) create mode 100644 sql-files/bot_accounts.sql create mode 100644 src/bot/Brokk/account.cpp create mode 100644 src/bot/Brokk/account.hpp create mode 100644 src/bot/Brokk/battle.cpp create mode 100644 src/bot/Brokk/battle.hpp create mode 100644 src/bot/Brokk/bot_account.cpp create mode 100644 src/bot/Brokk/bot_account.hpp create mode 100644 src/bot/Brokk/char_creation_helpers.cpp create mode 100644 src/bot/Brokk/char_creation_helpers.hpp create mode 100644 src/bot/Brokk/charakters.cpp create mode 100644 src/bot/Brokk/charakters.hpp create mode 100644 src/bot/Brokk/config_helpers.cpp create mode 100644 src/bot/Brokk/config_helpers.hpp create mode 100644 src/bot/Brokk/helper_skills.cpp create mode 100644 src/bot/Brokk/helper_skills.hpp create mode 100644 src/bot/Brokk/json.hpp create mode 100644 src/bot/Brokk/template_conf_bot_acc.json diff --git a/CMakeLists.txt b/CMakeLists.txt index ef35b46..4d000b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -521,6 +521,8 @@ if (WIN32) "${CMAKE_CURRENT_SOURCE_DIR}/tools/charserv.bat" "${CMAKE_CURRENT_SOURCE_DIR}/tools/logserv.bat" "${CMAKE_CURRENT_SOURCE_DIR}/tools/mapserv.bat" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/brokkserver.bat" + "${CMAKE_CURRENT_SOURCE_DIR}/tools/sindriserver.bat" "${CMAKE_CURRENT_SOURCE_DIR}/tools/runserver.bat" "${CMAKE_CURRENT_SOURCE_DIR}/tools/serv.bat" ) diff --git a/conf/char_athena.conf b/conf/char_athena.conf index 726dd30..c6dee8e 100644 --- a/conf/char_athena.conf +++ b/conf/char_athena.conf @@ -12,7 +12,7 @@ passwd: p1 // NOTE: Do not use spaces or any of these characters which are not allowed in // Windows filenames \/:*?"<>| // ... or else guild emblems won't work client-side! -server_name: rAthena +server_name: SanderRO // Wisp name for server: used to send wisp from server to players (between 4 to 23 characters) wisp_server_name: Server @@ -214,7 +214,7 @@ db_path: db // NOTE: Requires client 2011-03-09aragexeRE or newer. // A window is opened before you can select your character and you will have to enter a pincode by using only your mouse. // Default: yes -pincode_enabled: yes +pincode_enabled: no // How often does a user have to change his pincode? // 0: never (default) diff --git a/conf/inter_athena.conf b/conf/inter_athena.conf index b445656..6d239cd 100644 --- a/conf/inter_athena.conf +++ b/conf/inter_athena.conf @@ -76,6 +76,24 @@ log_db_db: ragnarok log_codepage: log_login_db: loginlog +// MySQL Brokk server +brokk_server_ip: 127.0.0.1 +brokk_server_port: 3306 +brokk_server_id: ragnarok +brokk_server_pw: ragnarok +brokk_server_db: ragnarok +brokk_codepage: +brokk_case_sensitive: no + +// MySQL Sindri server +sindri_server_ip: 127.0.0.1 +sindri_server_port: 3306 +sindri_server_id: ragnarok +sindri_server_pw: ragnarok +sindri_server_db: ragnarok +sindri_codepage: +sindri_case_sensitive: no + // MySQL Reconnect Settings // - mysql_reconnect_type: // 1: When MySQL disconnects during runtime, the server tries to reconnect diff --git a/conf/login_athena.conf b/conf/login_athena.conf index 734d3bd..f4407db 100644 --- a/conf/login_athena.conf +++ b/conf/login_athena.conf @@ -53,7 +53,7 @@ console: off // Can you use _M/_F to make new accounts on the server? // Note: This only works if client side password encryption is not enabled. -new_account: no +new_account: yes // If new_account is enabled, changes the minimum length for the account name. // By default is set to '4' or '6' (depending on the new login UI). diff --git a/sql-files/bot_accounts.sql b/sql-files/bot_accounts.sql new file mode 100644 index 0000000..f373192 --- /dev/null +++ b/sql-files/bot_accounts.sql @@ -0,0 +1,11 @@ +-- +-- Table structure for table `bot_accounts` +-- Creator id = 0 -> Created by Brokk via Auto-Generate from Configs +-- +CREATE TABLE IF NOT EXISTS `bot_accounts` ( + `account_id` int(11) unsigned NOT NULL, + `is_bot` tinyint(2) NOT NULL default '1', + `creator_id` int(11) unsigned NOT NULL default '0', + `creator_name` varchar(23) NOT NULL default 'Brokk', + PRIMARY KEY (`account_id`) +) ENGINE=MyISAM; \ No newline at end of file diff --git a/sql-files/main.sql b/sql-files/main.sql index 95cdbf2..3954d9b 100644 --- a/sql-files/main.sql +++ b/sql-files/main.sql @@ -1126,3 +1126,7 @@ CREATE TABLE IF NOT EXISTS `vendings` ( `autotrade` tinyint(4) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM; + +-- Add bot_accounts table +SOURCE bot_accounts.sql; + diff --git a/src/bot/Brokk/Brokk.vcxproj b/src/bot/Brokk/Brokk.vcxproj index 0ee1c6e..84b8a78 100644 --- a/src/bot/Brokk/Brokk.vcxproj +++ b/src/bot/Brokk/Brokk.vcxproj @@ -113,7 +113,7 @@ stdcpp17 MultiThreadedDebug - $(SolutionDir)src;$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src;$(SolutionDir)3rdparty\libconfig\;%(AdditionalIncludeDirectories) + $(SolutionDir)src;$(SolutionDir)3rdparty\rapidyaml\src;$(SolutionDir)3rdparty\rapidyaml\ext\c4core\src;$(SolutionDir)3rdparty\libconfig\;$(SolutionDir)src\login;$(SolutionDir)src\map;$(SolutionDir)src\char;%(AdditionalIncludeDirectories) true @@ -139,10 +139,21 @@ + + + + + + + + + + + diff --git a/src/bot/Brokk/Brokk.vcxproj.filters b/src/bot/Brokk/Brokk.vcxproj.filters index d794c4e..dae25ce 100644 --- a/src/bot/Brokk/Brokk.vcxproj.filters +++ b/src/bot/Brokk/Brokk.vcxproj.filters @@ -18,10 +18,43 @@ Quelldateien + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + + + Quelldateien + Headerdateien + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + \ No newline at end of file diff --git a/src/bot/Brokk/account.cpp b/src/bot/Brokk/account.cpp new file mode 100644 index 0000000..89ea05b --- /dev/null +++ b/src/bot/Brokk/account.cpp @@ -0,0 +1,954 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "account.hpp" + +#include //min / max +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "brokk.hpp" // brokk_config + +/// global defines + +/// internal structure +typedef struct AccountDB_SQL { + AccountDB vtable; // public interface + Sql* accounts; // SQL handle accounts storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = ""; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char account_db[32]; + char global_acc_reg_num_table[32]; + char global_acc_reg_str_table[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 uint32 account_id); +static bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ); +static bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ); +static bool account_db_sql_remove_webtokens( AccountDB* self ); +static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc, bool refresh_token); +static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const uint32 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, uint32 account_id); +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new, bool refresh_token); + +/// public constructor +AccountDB* account_db_sql(void) { + AccountDB_SQL* db = (AccountDB_SQL*)aCalloc(1, sizeof(AccountDB_SQL)); + new(db) 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.enable_webtoken = &account_db_sql_enable_webtoken; + db->vtable.disable_webtoken = &account_db_sql_disable_webtoken; + db->vtable.remove_webtokens = &account_db_sql_remove_webtokens; + 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 = nullptr; + // other settings + db->case_sensitive = false; + safestrncpy(db->account_db, "login", sizeof(db->account_db)); + safestrncpy(db->global_acc_reg_num_table, "global_acc_reg_num", sizeof(db->global_acc_reg_num_table)); + safestrncpy(db->global_acc_reg_str_table, "global_acc_reg_str", sizeof(db->global_acc_reg_str_table)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool account_db_sql_init(AccountDB* self) { + AccountDB_SQL* db = (AccountDB_SQL*)self; + Sql* sql_handle; + + db->accounts = Sql_Malloc(); + sql_handle = db->accounts; + + if( SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()) ) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->accounts); + db->accounts = nullptr; + return false; + } + + if( !db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str()) ) + Sql_ShowDebug(sql_handle); + + self->remove_webtokens( self ); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void account_db_sql_destroy(AccountDB* self){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + } + + Sql_Free(db->accounts); + db->accounts = nullptr; + + db->~AccountDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +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 = "login_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "account_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->account_db); + else + if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_table); + else + if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_table); + else + return false;// not found + return true; + } + + signature = "login_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +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 = "login_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "account_db") == 0 ) + safestrncpy(db->account_db, value, sizeof(db->account_db)); + else + if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + safestrncpy(db->global_acc_reg_str_table, value, sizeof(db->global_acc_reg_str_table)); + else + if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + safestrncpy(db->global_acc_reg_num_table, value, sizeof(db->global_acc_reg_num_table)); + else + return false;// not found + return true; + } + + signature = "login_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + 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. + * @param self: pointer to db + * @param acc: pointer of mmo_account to save + * @return true if successful, false if something has failed + */ +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 + uint32 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 != nullptr ) ? atoi(data) : 0; + Sql_FreeResult(sql_handle); + account_id = max((uint32_t) START_ACCOUNT_NUM, account_id); + } + + // 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, false); +} + +/** + * Delete an existing account entry and its regs. + * @param self: pointer to db + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool account_db_sql_remove(AccountDB* self, const uint32 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->global_acc_reg_num_table, account_id) + || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = %d", db->global_acc_reg_str_table, 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 new data provided (both account and regs). + * @param self: pointer to db + * @param acc: pointer of mmo_account to save + * @return true if successful, false if something has failed + */ +static bool account_db_sql_save(AccountDB* self, const struct mmo_account* acc, bool refresh_token) { + AccountDB_SQL* db = (AccountDB_SQL*)self; + return mmo_auth_tosql(db, acc, false, refresh_token); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to mmo_auth_fromsql. + * @param self: pointer to db + * @param acc: pointer of mmo_account to fill + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool account_db_sql_load_num(AccountDB* self, struct mmo_account* acc, const uint32 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. + * Doesn't actually retrieve data yet: escapes and checks userid, then transforms it to accid for fetching. + * Filled data structure is done by delegation to account_db_sql_load_num. + * @param self: pointer to db + * @param acc: pointer of mmo_account to fill + * @param userid: name of user account + * @return true if successful, false if something has failed + */ +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]; + uint32 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, nullptr); + account_id = atoi(data); + + return account_db_sql_load_num(self, acc, account_id); +} + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db 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). + * @param self: pointer to db iterator + */ +static void account_db_sql_iter_destroy(AccountDBIterator* self) { + AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next account in the database. + * @param self: pointer to db iterator + * @param acc: pointer of mmo_account to fill + * @return true if next account found and filled, false if something has failed + */ +static bool account_db_sql_iter_next(AccountDBIterator* self, struct mmo_account* acc) { + AccountDBIterator_SQL* iter = (AccountDBIterator_SQL*)self; + AccountDB_SQL* db = iter->db; + Sql* sql_handle = db->accounts; + 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, nullptr) && + data != nullptr ) + {// get account data + uint32 account_id; + 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; +} + +/** + * Fetch a struct mmo_account from sql, excluding web_auth_token. + * @param db: pointer to db + * @param acc: pointer of mmo_account to fill + * @param account_id: id of user account to take data from + * @return true if successful, false if something has failed + */ +static bool mmo_auth_fromsql(AccountDB_SQL* db, struct mmo_account* acc, uint32 account_id) { + Sql* sql_handle = db->accounts; + char* data; + + // retrieve login entry for the specified account + if( SQL_ERROR == Sql_Query(sql_handle, +#ifdef VIP_ENABLE + "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`group_id`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip`,`birthdate`,`character_slots`,`pincode`, `pincode_change`, `vip_time`, `old_group` FROM `%s` WHERE `account_id` = %d", +#else + "SELECT `account_id`,`userid`,`user_pass`,`sex`,`email`,`group_id`,`state`,`unban_time`,`expiration_time`,`logincount`,`lastlogin`,`last_ip`,`birthdate`,`character_slots`,`pincode`, `pincode_change` FROM `%s` WHERE `account_id` = %d", +#endif + 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, nullptr); acc->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, nullptr); safestrncpy(acc->userid, data, sizeof(acc->userid)); + Sql_GetData(sql_handle, 2, &data, nullptr); safestrncpy(acc->pass, data, sizeof(acc->pass)); + Sql_GetData(sql_handle, 3, &data, nullptr); acc->sex = data[0]; + Sql_GetData(sql_handle, 4, &data, nullptr); safestrncpy(acc->email, data, sizeof(acc->email)); + Sql_GetData(sql_handle, 5, &data, nullptr); acc->group_id = (unsigned int) atoi(data); + Sql_GetData(sql_handle, 6, &data, nullptr); acc->state = (unsigned int) strtoul(data, nullptr, 10); + Sql_GetData(sql_handle, 7, &data, nullptr); acc->unban_time = atol(data); + Sql_GetData(sql_handle, 8, &data, nullptr); acc->expiration_time = atol(data); + Sql_GetData(sql_handle, 9, &data, nullptr); acc->logincount = (unsigned int) strtoul(data, nullptr, 10); + Sql_GetData(sql_handle, 10, &data, nullptr); safestrncpy(acc->lastlogin, data==nullptr?"":data, sizeof(acc->lastlogin)); + Sql_GetData(sql_handle, 11, &data, nullptr); safestrncpy(acc->last_ip, data, sizeof(acc->last_ip)); + Sql_GetData(sql_handle, 12, &data, nullptr); safestrncpy(acc->birthdate, data==nullptr?"":data, sizeof(acc->birthdate)); + Sql_GetData(sql_handle, 13, &data, nullptr); acc->char_slots = (uint8) atoi(data); + Sql_GetData(sql_handle, 14, &data, nullptr); safestrncpy(acc->pincode, data, sizeof(acc->pincode)); + Sql_GetData(sql_handle, 15, &data, nullptr); acc->pincode_change = atol(data); +#ifdef VIP_ENABLE + Sql_GetData(sql_handle, 16, &data, nullptr); acc->vip_time = atol(data); + Sql_GetData(sql_handle, 17, &data, nullptr); acc->old_group = atoi(data); +#endif + Sql_FreeResult(sql_handle); + acc->web_auth_token[0] = '\0'; + + if( acc->char_slots > MAX_CHARS ){ + ShowError( "Account %s (AID=%u) exceeds MAX_CHARS. Capping...\n", acc->userid, acc->account_id ); + acc->char_slots = MAX_CHARS; + } + + return true; +} + +/** + * Save a struct mmo_account in sql. + * @param db: pointer to db + * @param acc: pointer of mmo_account to save + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool mmo_auth_tosql(AccountDB_SQL* db, const struct mmo_account* acc, bool is_new, bool refresh_token) { + Sql* sql_handle = db->accounts; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // 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, +#ifdef VIP_ENABLE + "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `group_id`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`, `birthdate`, `character_slots`, `pincode`, `pincode_change`, `vip_time`, `old_group` ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +#else + "INSERT INTO `%s` (`account_id`, `userid`, `user_pass`, `sex`, `email`, `group_id`, `state`, `unban_time`, `expiration_time`, `logincount`, `lastlogin`, `last_ip`, `birthdate`, `character_slots`, `pincode`, `pincode_change`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +#endif + 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, acc->lastlogin[0]?SQLDT_STRING:SQLDT_NULL, (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, acc->birthdate[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->birthdate, strlen(acc->birthdate)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_STRING, (void*)&acc->pincode, strlen(acc->pincode)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change)) +#ifdef VIP_ENABLE + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_LONG, (void*)&acc->vip_time, sizeof(acc->vip_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 17, SQLDT_INT, (void*)&acc->old_group, sizeof(acc->old_group)) +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + {// update account table + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=?,`character_slots`=?,`pincode`=?, `pincode_change`=?, `vip_time`=?, `old_group`=? WHERE `account_id` = '%d'", +#else + "UPDATE `%s` SET `userid`=?,`user_pass`=?,`sex`=?,`email`=?,`group_id`=?,`state`=?,`unban_time`=?,`expiration_time`=?,`logincount`=?,`lastlogin`=?,`last_ip`=?,`birthdate`=?,`character_slots`=?,`pincode`=?, `pincode_change`=? WHERE `account_id` = '%d'", +#endif + 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, acc->lastlogin[0]?SQLDT_STRING:SQLDT_NULL, (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, acc->birthdate[0]?SQLDT_STRING:SQLDT_NULL, (void*)&acc->birthdate, strlen(acc->birthdate)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_UCHAR, (void*)&acc->char_slots, sizeof(acc->char_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_STRING, (void*)&acc->pincode, strlen(acc->pincode)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_LONG, (void*)&acc->pincode_change, sizeof(acc->pincode_change)) +#ifdef VIP_ENABLE + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_LONG, (void*)&acc->vip_time, sizeof(acc->vip_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_INT, (void*)&acc->old_group, sizeof(acc->old_group)) +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + + if( acc->sex != 'S' && brokk_config.use_web_auth_token && refresh_token ){ + static bool initialized = false; + static const char* query; + + // Pseudo Scope to break out + while( !initialized ){ + if( SQL_SUCCESS == Sql_Query( sql_handle, "SELECT SHA2( 'test', 256 )" ) ){ + query = "UPDATE `%s` SET `web_auth_token` = LEFT( SHA2( CONCAT( UUID(), RAND() ), 256 ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'"; + initialized = true; + break; + } + + if( SQL_SUCCESS == Sql_Query( sql_handle, "SELECT MD5( 'test' )" ) ){ + query = "UPDATE `%s` SET `web_auth_token` = LEFT( MD5( CONCAT( UUID(), RAND() ) ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'"; + initialized = true; + break; + } + + ShowWarning( "Your MySQL does not support SHA2 and MD5 - no hashing will be used for login token creation.\n" ); + ShowWarning( "If you are using an old version of MySQL consider upgrading to a newer release.\n" ); + query = "UPDATE `%s` SET `web_auth_token` = LEFT( CONCAT( UUID(), RAND() ), %d ), `web_auth_token_enabled` = '1' WHERE `account_id` = '%d'"; + initialized = true; + break; + } + + const int MAX_RETRIES = 20; + int i = 0; + bool success = false; + + // Retry it for a maximum number of retries + do{ + if( SQL_SUCCESS == Sql_Query( sql_handle, query, db->account_db, WEB_AUTH_TOKEN_LENGTH - 1, acc->account_id ) ){ + success = true; + break; + } + }while( i < MAX_RETRIES && Sql_GetError( sql_handle ) == 1062 ); + + if( !success ){ + if( i == MAX_RETRIES ){ + ShowError( "Failed to generate a unique web_auth_token with %d retries...\n", i ); + }else{ + Sql_ShowDebug( sql_handle ); + } + + break; + } + + char* data; + size_t len; + + if( SQL_SUCCESS != Sql_Query( sql_handle, "SELECT `web_auth_token` from `%s` WHERE `account_id` = '%d'", db->account_db, acc->account_id ) || + SQL_SUCCESS != Sql_NextRow( sql_handle ) || + SQL_SUCCESS != Sql_GetData( sql_handle, 0, &data, &len ) + ){ + Sql_ShowDebug( sql_handle ); + break; + } + + safestrncpy( (char *)&acc->web_auth_token, data, sizeof( acc->web_auth_token ) ); + + Sql_FreeResult( sql_handle ); + } + + // 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; +} + +void mmo_save_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + uint16 count = RFIFOW(fd, 12); + + if (count) { + int cursor = 14, i; + char key[32], sval[254], esc_key[32*2+1], esc_sval[254*2+1]; + + for (i = 0; i < count; i++) { + uint32 index; + safestrncpy(key, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + Sql_EscapeString(sql_handle, esc_key, key); + cursor += RFIFOB(fd, cursor) + 1; + + index = RFIFOL(fd, cursor); + cursor += 4; + + switch (RFIFOB(fd, cursor++)) { + // int + case 0: + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%" PRId64 "')", db->global_acc_reg_num_table, account_id, esc_key, index, RFIFOQ(fd, cursor)) ) + Sql_ShowDebug(sql_handle); + cursor += 8; + break; + case 1: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_num_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + // str + case 2: + safestrncpy(sval, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + cursor += RFIFOB(fd, cursor) + 1; + Sql_EscapeString(sql_handle, esc_sval, sval); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%s')", db->global_acc_reg_str_table, account_id, esc_key, index, esc_sval) ) + Sql_ShowDebug(sql_handle); + break; + case 3: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_str_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + default: + ShowError("mmo_save_global_accreg: unknown type %d\n",RFIFOB(fd, cursor - 1)); + return; + } + } + } +} + +void mmo_send_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + char* data; + int16 plen = 0; + size_t len; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_str_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + /** + * Vessel! + * + * str type + * { keyLength(B), key(), index(L), valLength(B), val() } + **/ + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + } + } + + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_num_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + /** + * Vessel! + * + * int type + * { keyLength(B), key(), index(L), value(L) } + **/ + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + + WFIFOQ(fd, plen) = strtoll(data,nullptr,10); + plen += 8; + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + + plen = 16; + } + } + + WFIFOB(fd, 12) = 1; + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); +} + +bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '1' WHERE `account_id` = '%u'", db->account_db, account_id ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +} + +/** + * Timered function to disable webtoken for user + * If the user is online, then they must have logged since we started the timer. + * In that case, do nothing. The new authtoken must be valid. + * @param tid: timer id + * @param tick: tick of execution + * @param id: user account id + * @param data: AccountDB // because we don't use singleton??? + * @return :0 + +TIMER_FUNC(account_disable_webtoken_timer){ + const struct online_login_data* p = login_get_online_user(id); + AccountDB_SQL* db = reinterpret_cast(data); + + if (p == nullptr) { + ShowInfo("Web Auth Token for account %d was disabled\n", id); + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '0' WHERE `account_id` = '%u'", db->account_db, id ) ){ + Sql_ShowDebug( db->accounts ); + return 0; + } + } + + return 0; +} +*/ + + +bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + //add_timer(gettick() + brokk_config.disable_webtoken_delay, account_disable_webtoken_timer, account_id, reinterpret_cast(db)); + + return true; +} + +bool account_db_sql_remove_webtokens( AccountDB* self ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL, `web_auth_token_enabled` = '0'", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +} diff --git a/src/bot/Brokk/account.hpp b/src/bot/Brokk/account.hpp new file mode 100644 index 0000000..1adeb3e --- /dev/null +++ b/src/bot/Brokk/account.hpp @@ -0,0 +1,149 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef ACCOUNT_HPP +#define ACCOUNT_HPP + +#include +#include // ACCOUNT_REG2_NUM, WEB_AUTH_TOKEN_LENGTH +#include + +typedef struct AccountDB AccountDB; +typedef struct AccountDBIterator AccountDBIterator; + + +// standard engines +AccountDB* account_db_sql(void); + +struct mmo_account { + uint32 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) + unsigned int group_id; // player group id + uint8 char_slots; // this accounts maximum character slots (maximum is limited to MAX_CHARS define in char server) + 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) + char pincode[PINCODE_LENGTH+1]; // pincode system + time_t pincode_change; // (timestamp): last time of pincode change + char web_auth_token[WEB_AUTH_TOKEN_LENGTH]; // web authentication token (randomized on each login) +#ifdef VIP_ENABLE + int old_group; + time_t vip_time; +#endif +}; + + +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: + /// + /// @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 uint32 account_id); + + /// Enables the web auth token for the given account id + bool (*enable_webtoken)(AccountDB* self, const uint32 account_id); + + /// Disables the web auth token for the given account id + bool (*disable_webtoken)(AccountDB* self, const uint32 account_id); + + /// Removes the web auth token for all accounts + bool (*remove_webtokens)(AccountDB* self); + + /// Modifies the data of an existing account. + /// Uses acc->account_id to identify the account. + /// + /// @param self Database + /// @param acc Account data + /// @param refresh_token Whether or not to refresh the web auth token + /// @return true if successful + bool (*save)(AccountDB* self, const struct mmo_account* acc, bool refresh_token); + + /// 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 uint32 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); +}; + +void mmo_send_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id); +void mmo_save_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id); + +#endif /* ACCOUNT_HPP */ diff --git a/src/bot/Brokk/battle.cpp b/src/bot/Brokk/battle.cpp new file mode 100644 index 0000000..52cf218 --- /dev/null +++ b/src/bot/Brokk/battle.cpp @@ -0,0 +1,11845 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#include "battle.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atcommand.hpp" +#include "battleground.hpp" +#include "chrif.hpp" +#include "clif.hpp" +#include "elemental.hpp" +#include "guild.hpp" +#include "homunculus.hpp" +#include "log.hpp" +#include "map.hpp" +#include "mercenary.hpp" +#include "mob.hpp" +#include "party.hpp" +#include "path.hpp" +#include "pc.hpp" +#include "pc_groups.hpp" +#include "pet.hpp" + +using namespace rathena; + +struct Battle_Config battle_config; +static struct eri *delay_damage_ers; //For battle delay damage structures. + +// Early declaration +int battle_get_weapon_element(struct Damage *wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, short weapon_position, bool calc_for_damage_only); +int battle_get_magic_element(struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv, int mflag); +int battle_get_misc_element(struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv, int mflag); +static void battle_calc_defense_reduction(struct Damage* wd, struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv); + +/** + * Returns the current/list skill used by the bl + * @param bl + * @return skill_id + */ +uint16 battle_getcurrentskill(struct block_list *bl) +{ + struct unit_data *ud; + + if( bl->type == BL_SKILL ) { + struct skill_unit *su = (struct skill_unit*)bl; + return (su && su->group?su->group->skill_id:0); + } + + ud = unit_bl2ud(bl); + + return (ud?ud->skill_id:0); +} + +/** + * Get random targeting enemy + * @param bl + * @param ap + * @return Found target (1) or not found (0) + */ +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; +} + +/** + * Returns list of targets + * @param target + * @return Target list + */ +struct block_list* battle_gettargeted(struct block_list *target) +{ + struct block_list *bl_list[24]; + int c = 0; + nullpo_retr(nullptr, target); + + memset(bl_list, 0, sizeof(bl_list)); + map_foreachinallrange(battle_gettargeted_sub, target, AREA_SIZE, BL_CHAR, bl_list, &c, target->id); + if ( c == 0 ) + return nullptr; + if( c > 24 ) + c = 24; + return bl_list[rnd()%c]; +} + +/** + * Returns the ID of the current targeted character of the passed bl + * @param bl + * @return Target Unit ID + * @author [Skotlex] + */ +int battle_gettarget(struct block_list* bl) +{ + + switch (bl->type) { + case BL_PC: return ((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 ((s_mercenary_data*)bl)->ud.target; + case BL_ELEM: return ((s_elemental_data*)bl)->ud.target; + } + + return 0; +} + +/** + * Get random enemy + * @param bl + * @param ap + * @return Found target (1) or not found (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; +} + +/** + * Returns list of enemies within given range + * @param target + * @param type + * @param range + * @return Target list + * @author [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_foreachinallrange(battle_getenemy_sub, target, range, type, bl_list, &c, target); + + if ( c == 0 ) + return nullptr; + + if( c > 24 ) + c = 24; + + return bl_list[rnd()%c]; +} + +/** + * Get random enemy within area + * @param bl + * @param ap + * @return Found target (1) or not found (0) + */ +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; +} + +/** + * Returns list of enemies within an area + * @param src + * @param x + * @param y + * @param range + * @param type + * @param ignore_id + * @return Target list + */ +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_foreachinallarea(battle_getenemyarea_sub, src->m, x - range, y - range, x + range, y + range, type, bl_list, &c, src, ignore_id); + + if( c == 0 ) + return nullptr; + if( c >= 24 ) + c = 23; + + return bl_list[rnd()%c]; +} + +/** +* Deals damage without delay, applies additional effects and triggers monster events +* This function is called from battle_delay_damage or battle_delay_damage_sub +* All other instances of battle damage should also go through this function (i.e. anything that displays a damage number) +* Consider calling this function or battle_fix_damage instead status_fix_damage directly +* @param src: Source of damage +* @param target: Target of damage +* @param damage: Damage to be dealt +* @param delay: Damage delay +* @param skill_lv: Level of skill used +* @param skill_id: ID of skill used +* @param dmg_lv: State of the attack (miss, etc.) +* @param attack_type: Type of the attack (BF_NORMAL|BF_SKILL|BF_SHORT|BF_LONG|BF_WEAPON|BF_MAGIC|BF_MISC) +* @param additional_effects: Whether additional effects should be applied (otherwise it's just damage+coma) +* @param isspdamage: If the damage is done to SP +* @param tick: Current tick +* @return HP+SP+AP (0 if HP/SP/AP remained unchanged) +*/ +int battle_damage(struct block_list *src, struct block_list *target, int64 damage, t_tick delay, uint16 skill_lv, uint16 skill_id, enum damage_lv dmg_lv, unsigned short attack_type, bool additional_effects, t_tick tick, bool isspdamage) { + int dmg_change; + map_session_data* sd = nullptr; + if (src) + sd = BL_CAST(BL_PC, src); + map_freeblock_lock(); + if (isspdamage) + dmg_change = status_fix_spdamage(src, target, damage, delay, skill_id); + else if (sd && battle_check_coma(*sd, *target, (e_battle_flag)attack_type)) + dmg_change = status_damage(src, target, damage, 0, delay, 16, skill_id); // Coma attack + else + dmg_change = status_fix_damage(src, target, damage, delay, skill_id); + if (attack_type && !status_isdead(*target) && additional_effects) + skill_additional_effect(src, target, skill_id, skill_lv, attack_type, dmg_lv, tick); + if (dmg_lv > ATK_BLOCK && attack_type && additional_effects) + skill_counter_additional_effect(src, target, skill_id, skill_lv, attack_type, tick); + // This is the last place where we have access to the actual damage type, so any monster events depending on type must be placed here + if (target->type == BL_MOB && additional_effects) { + mob_data *md = BL_CAST(BL_MOB, target); + + if (md != nullptr) { + // Trigger monster skill condition for non-skill attacks. + if (!status_isdead(*target) && src != target) { + if (damage > 0) + mobskill_event(md, src, tick, attack_type, damage); + if (skill_id > 0) + mobskill_event(md, src, tick, MSC_SKILLUSED | (skill_id << 16)); + } + + // Monsters differentiate whether they have been attacked by a skill or a normal attack + if (damage > 0 && (attack_type & BF_NORMAL)) + md->norm_attacked_id = md->attacked_id; + } + } + map_freeblock_unlock(); + return dmg_change; +} + +/// Damage Delayed Structure +struct delay_damage { + int src_id; + int target_id; + int64 damage; + t_tick delay; + unsigned short distance; + uint16 skill_lv; + uint16 skill_id; + enum damage_lv dmg_lv; + unsigned short attack_type; + bool additional_effects; + enum bl_type src_type; + bool isspdamage; +}; + +TIMER_FUNC(battle_delay_damage_sub){ + struct delay_damage *dat = (struct delay_damage *)data; + + if ( dat ) { + struct block_list* src = map_id2bl(dat->src_id); + struct block_list* target = map_id2bl(dat->target_id); + + if (target && !status_isdead(*target)) { + 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] + { + //Deal damage + battle_damage(src, target, dat->damage, dat->delay, dat->skill_lv, dat->skill_id, dat->dmg_lv, dat->attack_type, dat->additional_effects, tick, dat->isspdamage); + } 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 + battle_fix_damage(target, target, dat->damage, dat->delay, dat->skill_id); + } + } + + map_session_data *sd = BL_CAST(BL_PC, src); + + if (sd && --sd->delayed_damage == 0 && sd->state.hold_recalc) { + sd->state.hold_recalc = false; + status_calc_pc(sd, SCO_FORCE); + } + } + ers_free(delay_damage_ers, dat); + return 0; +} + +int battle_delay_damage(t_tick tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int64 damage, enum damage_lv dmg_lv, t_tick ddelay, bool additional_effects, bool isspdamage) +{ + struct delay_damage *dat; + status_change *sc; + struct block_list *d_tbl = nullptr; + struct block_list *e_tbl = nullptr; + + nullpo_ret(src); + nullpo_ret(target); + + sc = status_get_sc(target); + + if (sc) { + if (sc->getSCE(SC_DEVOTION) && sc->getSCE(SC_DEVOTION)->val1) + d_tbl = map_id2bl(sc->getSCE(SC_DEVOTION)->val1); + if (sc->getSCE(SC_WATER_SCREEN_OPTION) && sc->getSCE(SC_WATER_SCREEN_OPTION)->val1) + e_tbl = map_id2bl(sc->getSCE(SC_WATER_SCREEN_OPTION)->val1); + } + + if( ((d_tbl && check_distance_bl(target, d_tbl, sc->getSCE(SC_DEVOTION)->val3)) || e_tbl) && + damage > 0 && skill_id != CR_REFLECTSHIELD +#ifndef RENEWAL + && skill_id != PA_PRESSURE +#endif + ) { + map_session_data* tsd = BL_CAST( BL_PC, target ); + + if( tsd && pc_issit( tsd ) && battle_config.devotion_standup_fix ){ + pc_setstand( tsd, true ); + skill_sit( tsd, 0 ); + } + + damage = 0; + } + + // The client refuses to display animations slower than 1x speed + // So we need to shorten AttackMotion to be in-sync with the client in this case + if (battle_config.synchronize_damage && skill_id == 0 && src->type == BL_MOB && amotion > status_get_clientamotion(src)) + amotion = status_get_clientamotion(src); + // Check for delay battle damage config + else if (!battle_config.delay_battle_damage) + amotion = 1; + // Aegis places a damage-delay cap of 1 sec to non player attacks + // We only want to apply this cap if damage was not synchronized + else if (src->type != BL_PC && amotion > 1000) + amotion = 1000; + + // Skip creation of timer + if (amotion <= 1) { + //Deal damage + battle_damage(src, target, damage, ddelay, skill_lv, skill_id, dmg_lv, attack_type, additional_effects, gettick(), isspdamage); + 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) + (battle_config.snap_dodge ? 10 : AREA_SIZE); + dat->additional_effects = additional_effects; + dat->src_type = src->type; + dat->isspdamage = isspdamage; + + if( src->type == BL_PC ) + ((TBL_PC*)src)->delayed_damage++; + + add_timer(tick+amotion, battle_delay_damage_sub, 0, (intptr_t)dat); + + return 0; +} + +int battle_fix_damage(struct block_list* src, struct block_list* target, int64 damage, t_tick walkdelay, uint16 skill_id) { + return battle_damage(src, target, damage, walkdelay, 0, skill_id, ATK_DEF, BF_MISC, false, gettick(), false); +} + +/** + * Does attribute fix modifiers. + * Added passing of the chars so that the status changes can affect it. [Skotlex] + * Note: Passing src/target == nullptr is perfectly valid, it skips SC_ checks. + * @param src + * @param target + * @param damage + * @param atk_elem + * @param def_type + * @param def_lv + * @param flag 0x1 = allow to return negative values even if config for healing through negative resist is disabled + * @return damage + */ +int64 battle_attr_fix(struct block_list *src, struct block_list *target, int64 damage,int atk_elem,int def_type, int def_lv, int flag) +{ + status_change *sc = nullptr, *tsc = nullptr; + int ratio; + + if (src) sc = status_get_sc(src); + if (target) tsc = status_get_sc(target); + + if (!CHK_ELEMENT(atk_elem)) + atk_elem = rnd()%ELE_ALL; + + if (!CHK_ELEMENT(def_type) || !CHK_ELEMENT_LEVEL(def_lv)) { + ShowError("battle_attr_fix: unknown attribute type: atk=%d def_type=%d def_lv=%d\n",atk_elem,def_type,def_lv); + return damage; + } + + ratio = elemental_attribute_db.getAttribute(def_lv-1, atk_elem, def_type); + if (sc && sc->count) { //increase dmg by src status + switch(atk_elem){ + case ELE_FIRE: + if (sc->getSCE(SC_VOLCANO)) +#ifdef RENEWAL + ratio += sc->getSCE(SC_VOLCANO)->val3; +#else + damage += (int64)((damage*sc->getSCE(SC_VOLCANO)->val3) / 100); +#endif + break; + case ELE_WIND: + if (sc->getSCE(SC_VIOLENTGALE)) +#ifdef RENEWAL + ratio += sc->getSCE(SC_VIOLENTGALE)->val3; +#else + damage += (int64)((damage*sc->getSCE(SC_VIOLENTGALE)->val3) / 100); +#endif + break; + case ELE_WATER: + if (sc->getSCE(SC_DELUGE)) +#ifdef RENEWAL + ratio += sc->getSCE(SC_DELUGE)->val3; +#else + damage += (int64)((damage*sc->getSCE(SC_DELUGE)->val3) / 100); +#endif + break; + case ELE_GHOST: + if (sc->getSCE(SC_TELEKINESIS_INTENSE)) + ratio += sc->getSCE(SC_TELEKINESIS_INTENSE)->val3; + break; + } + } + + if( target && target->type == BL_SKILL ) { + if( atk_elem == ELE_FIRE && battle_getcurrentskill(target) == GN_WALLOFTHORN ) { + struct skill_unit *su = (struct skill_unit*)target; + std::shared_ptr sg; + struct block_list *src2; + + if( !su || !su->alive || (sg = su->group) == nullptr || !sg || sg->val3 == -1 || + (src2 = map_id2bl(sg->src_id)) == nullptr || status_isdead(*src2) ) + return 0; + + if( sg->unit_id != UNT_FIREWALL ) { + int x,y; + x = sg->val3 >> 16; + y = sg->val3 & 0xffff; + skill_unitsetting(src2,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) { //increase dmg by target status + switch(atk_elem) { + case ELE_FIRE: + if (tsc->getSCE(SC_SPIDERWEB)) { //Double damage +#ifdef RENEWAL + ratio += 100; +#else + damage *= 2; +#endif + //Remove a unit group or end whole status change + status_change_end(target, SC_SPIDERWEB); + } + if (tsc->getSCE(SC_THORNSTRAP) && battle_getcurrentskill(src) != GN_CARTCANNON) + status_change_end(target, SC_THORNSTRAP); + if (tsc->getSCE(SC_CRYSTALIZE)) + status_change_end(target, SC_CRYSTALIZE); + if (tsc->getSCE(SC_EARTH_INSIGNIA)) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + if( tsc->getSCE(SC_WIDEWEB) ) { +#ifdef RENEWAL + ratio += 100; +#else + damage *= 2; +#endif + status_change_end(target,SC_WIDEWEB); + } + if( tsc->getSCE(SC_BURNT) ) { +#ifdef RENEWAL + ratio += 400; +#else + damage += (int64)(damage * 400 / 100); +#endif + } + break; + case ELE_HOLY: + if (tsc->getSCE(SC_ORATIO)) +#ifdef RENEWAL + ratio += tsc->getSCE(SC_ORATIO)->val1 * 2; +#else + damage += (int64)(damage * (tsc->getSCE(SC_ORATIO)->val1 * 2) / 100); +#endif + break; + case ELE_POISON: + if (tsc->getSCE(SC_VENOMIMPRESS)) +#ifdef RENEWAL + ratio += tsc->getSCE(SC_VENOMIMPRESS)->val2; +#else + damage += (int64)(damage * tsc->getSCE(SC_VENOMIMPRESS)->val2 / 100); +#endif + if (tsc->getSCE(SC_CLOUD_POISON)) { +#ifdef RENEWAL + ratio += 5 * tsc->getSCE(SC_CLOUD_POISON)->val1; +#else + damage += (int64)(damage * 5 * tsc->getSCE(SC_CLOUD_POISON)->val1 / 100); +#endif + } + break; + case ELE_WIND: + if (tsc->getSCE(SC_WATER_INSIGNIA)) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + if (tsc->getSCE(SC_CRYSTALIZE)) { + uint16 skill_id = battle_getcurrentskill(src); + + if (skill_get_type(skill_id)&BF_MAGIC) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + } + break; + case ELE_WATER: + if (tsc->getSCE(SC_FIRE_INSIGNIA)) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + if (tsc->getSCE(SC_MISTYFROST)) +#ifdef RENEWAL + ratio += 15; +#else + damage += (int64)(damage * 15 / 100); +#endif + break; + case ELE_EARTH: + if (tsc->getSCE(SC_WIND_INSIGNIA)) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + status_change_end(target, SC_MAGNETICFIELD); //freed if received earth dmg + break; + case ELE_NEUTRAL: + if (tsc->getSCE(SC_ANTI_M_BLAST)) +#ifdef RENEWAL + ratio += tsc->getSCE(SC_ANTI_M_BLAST)->val2; +#else + damage += (int64)(damage * tsc->getSCE(SC_ANTI_M_BLAST)->val2 / 100); +#endif + break; + case ELE_DARK: + if (tsc->getSCE(SC_SOULCURSE)) { + if (status_get_class_(target) == CLASS_BOSS) +#ifdef RENEWAL + ratio += 20; +#else + damage += (int64)(damage * 20 / 100); +#endif + else +#ifdef RENEWAL + ratio += 100; +#else + damage *= 2; +#endif + } + break; + } + + if (tsc->getSCE(SC_MAGIC_POISON)) +#ifdef RENEWAL + ratio += 50; +#else + damage += (int64)(damage * 50 / 100); +#endif + } + + if (battle_config.attr_recover == 0 && !(flag & 1) && ratio < 0) + ratio = 0; + +#ifdef RENEWAL + //In renewal, reductions are always rounded down so damage can never reach 0 unless ratio is 0 + damage = damage - (int64)((damage * (100 - ratio)) / 100); +#else + damage = (int64)((damage*ratio)/100); +#endif + + //Damage can be negative, see battle_config.attr_recover + return damage; +} + +/** + * Calculates card bonuses damage adjustments. + * @param attack_type @see enum e_battle_flag + * @param src Attacker + * @param target Target + * @param nk Skill's nk @see enum e_skill_nk [NK_IGNOREATKCARD|NK_IGNOREELEMENT|NK_IGNOREDEFCARD] + * @param rh_ele Right-hand weapon element + * @param lh_ele Left-hand weapon element (BF_MAGIC and BF_MISC ignore this value) + * @param damage Original damage + * @param left Left hand flag (BF_MISC and BF_MAGIC ignore flag value) + * 3: Calculates attacker bonuses in both hands. + * 2: Calculates attacker bonuses in right-hand only. + * 0 or 1: Only calculates target bonuses. + * @param flag Misc value of skill & damage flags + * @return damage Damage diff between original damage and after calculation + */ +int battle_calc_cardfix(int attack_type, struct block_list *src, struct block_list *target, std::bitset nk, int rh_ele, int lh_ele, int64 damage, int left, int flag){ + map_session_data *sd, ///< Attacker session data if BL_PC + *tsd; ///< Target session data if BL_PC + int cardfix = 1000; + int s_class, ///< Attacker class + t_class; ///< Target class + std::vector s_race2, /// Attacker Race2 + t_race2; ///< Target Race2 + enum e_element s_defele; ///< Attacker Element (not a weapon or skill element!) + int64 original_damage; + + if( !damage ) + return 0; + + original_damage = damage; + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + t_class = status_get_class(target); + s_class = status_get_class(src); + ///< Attacker status data + status_data* sstatus = status_get_status_data(*src); + ///< Target status data + status_data* tstatus = status_get_status_data(*target); + s_race2 = status_get_race2(src); + t_race2 = status_get_race2(target); + s_defele = (tsd) ? (enum e_element)status_get_element(src) : ELE_NONE; + + // When the attacker is a monster, then all bonuses on BF_WEAPON will work and no bonuses on BF_MAGIC + // Does not impact the attack type + if (src && src->type == BL_MOB && battle_config.cardfix_monster_physical) { + flag |= BF_WEAPON; + flag &= ~BF_MAGIC; + } + +//Official servers apply the cardfix value on a base of 1000 and round down the reduction/increase +#define APPLY_CARDFIX(damage, fix) { (damage) = (damage) - (int64)(((damage) * (1000 - max(0, fix))) / 1000); } + + switch( attack_type ) { + case BF_MAGIC: + // Affected by attacker ATK bonuses + if( sd && !nk[NK_IGNOREATKCARD] ) { + int32 race2_val = 0; + + for (const auto &raceit : t_race2) + race2_val += sd->indexed_bonus.magic_addrace2[raceit]; + cardfix = cardfix * (100 + sd->indexed_bonus.magic_addrace[tstatus->race] + sd->indexed_bonus.magic_addrace[RC_ALL] + race2_val) / 100; + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + cardfix = cardfix * (100 + sd->indexed_bonus.magic_addele[tstatus->def_ele] + sd->indexed_bonus.magic_addele[ELE_ALL] + + sd->indexed_bonus.magic_addele_script[tstatus->def_ele] + sd->indexed_bonus.magic_addele_script[ELE_ALL]) / 100; + cardfix = cardfix * (100 + sd->indexed_bonus.magic_atk_ele[rh_ele] + sd->indexed_bonus.magic_atk_ele[ELE_ALL]) / 100; + } + cardfix = cardfix * (100 + sd->indexed_bonus.magic_addsize[tstatus->size] + sd->indexed_bonus.magic_addsize[SZ_ALL]) / 100; + cardfix = cardfix * (100 + sd->indexed_bonus.magic_addclass[tstatus->class_] + sd->indexed_bonus.magic_addclass[CLASS_ALL]) / 100; + for (const auto &it : sd->add_mdmg) { + if (it.id == t_class) { + cardfix = cardfix * (100 + it.val) / 100; + break; + } + } + APPLY_CARDFIX(damage, cardfix); + } + + // Affected by target DEF bonuses + if( tsd && !nk[NK_IGNOREDEFCARD] ) { + cardfix = 1000; // reset var for target + + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL]; + + for (const auto &it : tsd->subele2) { + if (it.ele != ELE_ALL && it.ele != rh_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + if (s_defele != ELE_NONE) + ele_fix += tsd->indexed_bonus.magic_subdefele[s_defele] + tsd->indexed_bonus.magic_subdefele[ELE_ALL]; + cardfix = cardfix * (100 - ele_fix) / 100; + } + cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.magic_subsize[sstatus->size] - tsd->indexed_bonus.magic_subsize[SZ_ALL]) / 100; + + int32 race_fix = 0; + + for (const auto &raceit : s_race2) + race_fix += tsd->indexed_bonus.subrace2[raceit]; + cardfix = cardfix * (100 - race_fix) / 100; + race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL]; + for (const auto &it : tsd->subrace3) { + if (it.race != RC_ALL && it.race != sstatus->race) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + race_fix += it.rate; + } + cardfix = cardfix * (100 - race_fix) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 100; + + for (const auto &it : tsd->add_mdef) { + if (it.id == s_class) { + cardfix = cardfix * (100 - it.val) / 100; + break; + } + } +#ifndef RENEWAL + //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 if (!nk[NK_IGNORELONGCARD]) + cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100; +#endif + cardfix = cardfix * (100 - tsd->bonus.magic_def_rate) / 100; + + if( tsd->sc.getSCE(SC_MDEF_RATE) ) + cardfix = cardfix * (100 - tsd->sc.getSCE(SC_MDEF_RATE)->val1) / 100; + APPLY_CARDFIX(damage, cardfix); + } + break; + + case BF_WEAPON: + // Affected by attacker ATK bonuses + if( sd && !nk[NK_IGNOREATKCARD] && (left&2) ) { + short cardfix_ = 1000; + + if( sd->state.arrow_atk ) { // Ranged attack + cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->indexed_bonus.arrow_addrace[tstatus->race] + + sd->right_weapon.addrace[RC_ALL] + sd->indexed_bonus.arrow_addrace[RC_ALL]) / 100; + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->indexed_bonus.arrow_addele[tstatus->def_ele] + + sd->right_weapon.addele[ELE_ALL] + sd->indexed_bonus.arrow_addele[ELE_ALL]; + + for (const auto &it : sd->right_weapon.addele2) { + if (it.ele != ELE_ALL && it.ele != tstatus->def_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + cardfix = cardfix * (100 + ele_fix) / 100; + } + cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->indexed_bonus.arrow_addsize[tstatus->size] + + sd->right_weapon.addsize[SZ_ALL] + sd->indexed_bonus.arrow_addsize[SZ_ALL]) / 100; + + int32 race_fix = 0; + + for (const auto &raceit : t_race2) + race_fix += sd->right_weapon.addrace2[raceit]; + cardfix = cardfix * (100 + race_fix) / 100; + cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->indexed_bonus.arrow_addclass[tstatus->class_] + + sd->right_weapon.addclass[CLASS_ALL] + sd->indexed_bonus.arrow_addclass[CLASS_ALL]) / 100; + } else { // Melee attack + int skill = 0; + + // Calculates each right & left hand weapon bonuses separatedly + if( !battle_config.left_cardfix_to_right ) { + // Right-handed weapon + cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->right_weapon.addrace[RC_ALL]) / 100; + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->right_weapon.addele[ELE_ALL]; + + for (const auto &it : sd->right_weapon.addele2) { + if (it.ele != ELE_ALL && it.ele != tstatus->def_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + cardfix = cardfix * (100 + ele_fix) / 100; + } + cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->right_weapon.addsize[SZ_ALL]) / 100; + for (const auto &raceit : t_race2) + cardfix = cardfix * (100 + sd->right_weapon.addrace2[raceit]) / 100; + cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->right_weapon.addclass[CLASS_ALL]) / 100; + + if( left&1 ) { // Left-handed weapon + cardfix_ = cardfix_ * (100 + sd->left_weapon.addrace[tstatus->race] + sd->left_weapon.addrace[RC_ALL]) / 100; + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix_lh = sd->left_weapon.addele[tstatus->def_ele] + sd->left_weapon.addele[ELE_ALL]; + + for (const auto &it : sd->left_weapon.addele2) { + if (it.ele != ELE_ALL && it.ele != tstatus->def_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix_lh += it.rate; + } + cardfix_ = cardfix_ * (100 + ele_fix_lh) / 100; + } + cardfix_ = cardfix_ * (100 + sd->left_weapon.addsize[tstatus->size] + sd->left_weapon.addsize[SZ_ALL]) / 100; + for (const auto &raceit : t_race2) + cardfix_ = cardfix_ * (100 + sd->left_weapon.addrace2[raceit]) / 100; + cardfix_ = cardfix_ * (100 + sd->left_weapon.addclass[tstatus->class_] + sd->left_weapon.addclass[CLASS_ALL]) / 100; + } + } + // Calculates right & left hand weapon as unity + else { + //! CHECKME: If 'left_cardfix_to_right' is yes, doesn't need to check NK_IGNOREELEMENT? + //if( !nk[&]K_IGNOREELEMENT) ) { // Affected by Element modifier bonuses + int ele_fix = sd->right_weapon.addele[tstatus->def_ele] + sd->left_weapon.addele[tstatus->def_ele] + + sd->right_weapon.addele[ELE_ALL] + sd->left_weapon.addele[ELE_ALL]; + + for (const auto &it : sd->right_weapon.addele2) { + if (it.ele != ELE_ALL && it.ele != tstatus->def_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + for (const auto &it : sd->left_weapon.addele2) { + if (it.ele != ELE_ALL && it.ele != tstatus->def_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + cardfix = cardfix * (100 + ele_fix) / 100; + //} + cardfix = cardfix * (100 + sd->right_weapon.addrace[tstatus->race] + sd->left_weapon.addrace[tstatus->race] + + sd->right_weapon.addrace[RC_ALL] + sd->left_weapon.addrace[RC_ALL]) / 100; + cardfix = cardfix * (100 + sd->right_weapon.addsize[tstatus->size] + sd->left_weapon.addsize[tstatus->size] + + sd->right_weapon.addsize[SZ_ALL] + sd->left_weapon.addsize[SZ_ALL]) / 100; + for (const auto &raceit : t_race2) + cardfix = cardfix * (100 + sd->right_weapon.addrace2[raceit] + sd->left_weapon.addrace2[raceit]) / 100; + cardfix = cardfix * (100 + sd->right_weapon.addclass[tstatus->class_] + sd->left_weapon.addclass[tstatus->class_] + + sd->right_weapon.addclass[CLASS_ALL] + sd->left_weapon.addclass[CLASS_ALL]) / 100; + } +#ifndef RENEWAL + if( sd->status.weapon == W_KATAR && (skill = pc_checkskill(sd,ASC_KATAR)) > 0 ) // Adv. Katar Mastery functions similar to a +%ATK card on official [helvetica] + cardfix = cardfix * (100 + (10 + 2 * skill)) / 100; +#endif + } + + //! CHECKME: These right & left hand weapon ignores 'left_cardfix_to_right'? + for (const auto &it : sd->right_weapon.add_dmg) { + if (it.id == t_class) { + cardfix = cardfix * (100 + it.val) / 100; + break; + } + } + if( left&1 ) { + for (const auto &it : sd->left_weapon.add_dmg) { + if (it.id == t_class) { + cardfix_ = cardfix_ * (100 + it.val) / 100; + break; + } + } + } +#ifndef RENEWAL + if (flag & BF_SHORT) + cardfix = cardfix * (100 + sd->bonus.short_attack_atk_rate) / 100; + if( flag&BF_LONG ) + cardfix = cardfix * (100 + sd->bonus.long_attack_atk_rate) / 100; +#endif + if (left&1) { + APPLY_CARDFIX(damage, cardfix_); + } else { + APPLY_CARDFIX(damage, cardfix); + } + } + // Affected by target DEF bonuses + else if( tsd && !nk[NK_IGNOREDEFCARD] && !(left&2) ) { + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL]; + + for (const auto &it : tsd->subele2) { + if (it.ele != ELE_ALL && it.ele != rh_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + cardfix = cardfix * (100 - ele_fix) / 100; + + if( left&1 && lh_ele != rh_ele ) { + int ele_fix_lh = tsd->indexed_bonus.subele[lh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[lh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL]; + + for (const auto &it : tsd->subele2) { + if (it.ele != ELE_ALL && it.ele != lh_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix_lh += it.rate; + } + cardfix = cardfix * (100 - ele_fix_lh) / 100; + } + + cardfix = cardfix * (100 - tsd->indexed_bonus.subdefele[s_defele] - tsd->indexed_bonus.subdefele[ELE_ALL]) / 100; + } + + int32 race_fix = 0; + + cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.weapon_subsize[sstatus->size] - tsd->indexed_bonus.weapon_subsize[SZ_ALL]) / 100; + for (const auto &raceit : s_race2) + race_fix += tsd->indexed_bonus.subrace2[raceit]; + cardfix = cardfix * (100 - race_fix) / 100; + race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL]; + for (const auto &it : tsd->subrace3) { + if (it.race != RC_ALL && it.race != sstatus->race) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + race_fix += it.rate; + } + cardfix = cardfix * (100 - race_fix) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 100; + for (const auto &it : tsd->add_def) { + if (it.id == s_class) { + cardfix = cardfix * (100 - it.val) / 100; + break; + } + } + if( flag&BF_SHORT ) + cardfix = cardfix * (100 - tsd->bonus.near_attack_def_rate) / 100; + else if (!nk[NK_IGNORELONGCARD]) // BF_LONG (there's no other choice) + cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100; + if( tsd->sc.getSCE(SC_DEF_RATE) ) + cardfix = cardfix * (100 - tsd->sc.getSCE(SC_DEF_RATE)->val1) / 100; + APPLY_CARDFIX(damage, cardfix); + } + break; + + case BF_MISC: + // Affected by target DEF bonuses + if( tsd && !nk[NK_IGNOREDEFCARD] ) { + if( !nk[NK_IGNOREELEMENT] ) { // Affected by Element modifier bonuses + int ele_fix = tsd->indexed_bonus.subele[rh_ele] + tsd->indexed_bonus.subele[ELE_ALL] + tsd->indexed_bonus.subele_script[rh_ele] + tsd->indexed_bonus.subele_script[ELE_ALL]; + + for (const auto &it : tsd->subele2) { + if (it.ele != rh_ele) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + ele_fix += it.rate; + } + if (s_defele != ELE_NONE) + ele_fix += tsd->indexed_bonus.subdefele[s_defele] + tsd->indexed_bonus.subdefele[ELE_ALL]; + cardfix = cardfix * (100 - ele_fix) / 100; + } + int race_fix = tsd->indexed_bonus.subrace[sstatus->race] + tsd->indexed_bonus.subrace[RC_ALL]; + for (const auto &it : tsd->subrace3) { + if (it.race != RC_ALL && it.race != sstatus->race) + continue; + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + race_fix += it.rate; + } + cardfix = cardfix * (100 - race_fix) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.subsize[sstatus->size] - tsd->indexed_bonus.subsize[SZ_ALL]) / 100; + race_fix = 0; + for (const auto &raceit : s_race2) + race_fix += tsd->indexed_bonus.subrace2[raceit]; + cardfix = cardfix * (100 - race_fix) / 100; + cardfix = cardfix * (100 - tsd->indexed_bonus.subclass[sstatus->class_] - tsd->indexed_bonus.subclass[CLASS_ALL]) / 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 if (!nk[NK_IGNORELONGCARD]) // BF_LONG (there's no other choice) + cardfix = cardfix * (100 - tsd->bonus.long_attack_def_rate) / 100; + APPLY_CARDFIX(damage, cardfix); + } + break; + } + +#undef APPLY_CARDFIX + + return (int)cap_value(damage - original_damage, INT_MIN, INT_MAX); +} + +/** +* Absorb damage based on criteria +* @param bl +* @param d Damage +**/ +static void battle_absorb_damage(struct block_list *bl, struct Damage *d) { + int64 dmg_ori = 0, dmg_new = 0; + + nullpo_retv(bl); + nullpo_retv(d); + + if (!d->damage && !d->damage2) + return; + + switch (bl->type) { + case BL_PC: + { + map_session_data *sd = BL_CAST(BL_PC, bl); + if (!sd) + return; + dmg_ori = dmg_new = d->damage + d->damage2; + if (sd->bonus.absorb_dmg_maxhp) { + int hp = sd->bonus.absorb_dmg_maxhp * status_get_max_hp(bl) / 100; + if (dmg_ori > hp) + dmg_new = dmg_ori - hp; + } + if (sd->bonus.absorb_dmg_maxhp2) { + int hp = sd->bonus.absorb_dmg_maxhp2 * status_get_max_hp(bl) / 100; + if (dmg_ori > hp) { + dmg_new = hp; + } + } + } + break; + } + + if (dmg_ori == dmg_new) + return; + + if (!d->damage2) + d->damage = dmg_new; + else if (!d->damage) + d->damage2 = dmg_new; + else { + d->damage = dmg_new; + d->damage2 = dmg_new * d->damage2 / dmg_ori / 100; + if (d->damage2 < 1) + d->damage2 = 1; + d->damage = d->damage - d->damage2; + } +} + +/** + * Check for active statuses that block damage + * @param src: Attacker + * @param target: Target of attack + * @param sc: Status Change data + * @param d: Damage data + * @param damage: Damage received as a reference + * @param skill_id: Skill ID + * @param skill_lv: Skill level + * @return True: Damage inflicted, False: Missed + **/ +bool battle_status_block_damage(struct block_list *src, struct block_list *target, status_change *sc, struct Damage *d, int64 &damage, uint16 skill_id, uint16 skill_lv) { + if (!src || !target || !sc || !d) + return true; + + status_change_entry *sce; + int flag = d->flag; + + // SC Types that must be first because they may or may not block damage + if ((sce = sc->getSCE(SC_KYRIE)) && damage > 0) { + sce->val2 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + 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(target, SC_KYRIE); + } + + int element; + if (flag & BF_WEAPON) { + status_data* sstatus = status_get_status_data(*src); + if(sstatus->rhw.ele == ELE_NEUTRAL && sstatus->lhw.ele > sstatus->rhw.ele) + element = battle_get_weapon_element(d, src, target, skill_id, skill_lv, EQI_HAND_L, false); + else + element = battle_get_weapon_element(d, src, target, skill_id, skill_lv, EQI_HAND_R, false); + } else if(flag & BF_MAGIC) + element = battle_get_magic_element(src, target, skill_id, skill_lv, d->miscflag); + else + element = battle_get_misc_element(src, target, skill_id, skill_lv, d->miscflag); + + switch( element ){ + case ELE_NEUTRAL: + if( sc->getSCE( SC_IMMUNE_PROPERTY_NOTHING ) ){ + damage = 0; + return false; + } + break; + case ELE_WATER: + if( sc->getSCE( SC_IMMUNE_PROPERTY_WATER ) ){ + damage = 0; + return false; + } + break; + case ELE_EARTH: + if( sc->getSCE( SC_IMMUNE_PROPERTY_GROUND ) ){ + damage = 0; + return false; + } + break; + case ELE_FIRE: + if( sc->getSCE( SC_IMMUNE_PROPERTY_FIRE ) ){ + damage = 0; + return false; + } + break; + case ELE_WIND: + if( sc->getSCE( SC_IMMUNE_PROPERTY_WIND ) ){ + damage = 0; + return false; + } + break; + case ELE_DARK: + if( sc->getSCE( SC_IMMUNE_PROPERTY_DARKNESS ) ){ + damage = 0; + return false; + } + break; + case ELE_HOLY: + if( sc->getSCE( SC_IMMUNE_PROPERTY_SAINT ) ){ + damage = 0; + return false; + } + break; + case ELE_POISON: + if( sc->getSCE( SC_IMMUNE_PROPERTY_POISON ) ){ + damage = 0; + return false; + } + break; + case ELE_GHOST: + if( sc->getSCE( SC_IMMUNE_PROPERTY_TELEKINESIS ) ){ + damage = 0; + return false; + } + break; + case ELE_UNDEAD: + if( sc->getSCE( SC_IMMUNE_PROPERTY_UNDEAD ) ){ + damage = 0; + return false; + } + break; + } + + if ((sce = sc->getSCE(SC_P_ALTER)) && damage > 0) { + clif_specialeffect(target, EF_GUARD, AREA); + sce->val3 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + if (sce->val3 >= 0) + damage = 0; + else + damage = -sce->val3; + if (sce->val3 <= 0) + status_change_end(target, SC_P_ALTER); + } + + if ((sce = sc->getSCE(SC_TUNAPARTY)) && damage > 0) { + sce->val2 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + if (sce->val2 >= 0) + damage = 0; + else + damage = -sce->val2; + if (sce->val2 <= 0) + status_change_end(target, SC_TUNAPARTY); + } + + if ((sce = sc->getSCE(SC_DIMENSION1)) && damage > 0) { + sce->val2 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + if (sce->val2 <= 0) + status_change_end(target, SC_DIMENSION1); + return false; + } + + if ((sce = sc->getSCE(SC_DIMENSION2)) && damage > 0) { + sce->val2 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + if (sce->val2 <= 0) + status_change_end(target, SC_DIMENSION2); + return false; + } + + if ((sce = sc->getSCE(SC_GUARDIAN_S)) && damage > 0) { + clif_specialeffect(target, EF_GUARD3, AREA);// Not official but we gotta show some way the barrier is working. [Rytech] + sce->val2 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + if (flag & BF_WEAPON) { + if (sce->val2 >= 0) + damage = 0; + else + damage = -sce->val2; + } + if (sce->val2 <= 0) + status_change_end(target, SC_GUARDIAN_S); + } + + // Weapon Blocking can be triggered while the above statuses are active. + if ((sce = sc->getSCE(SC_WEAPONBLOCKING)) && flag & (BF_SHORT | BF_WEAPON) && rnd() % 100 < sce->val2) { + clif_skill_nodamage(target, src, GC_WEAPONBLOCKING, sce->val1, 1); + sc_start(src, target, SC_WEAPONBLOCK_ON, 100, src->id, skill_get_time2(GC_WEAPONBLOCKING, sce->val1)); + d->dmg_lv = ATK_BLOCK; + return false; + } + + if (damage == 0) + return false; + + // ATK_BLOCK Type + if ((sce = sc->getSCE(SC_SAFETYWALL)) && (flag&(BF_SHORT | BF_MAGIC)) == BF_SHORT) { + std::shared_ptr group = skill_id2group(sce->val3); + + if (group) { + d->dmg_lv = ATK_BLOCK; + + switch (sce->val2) { + case MG_SAFETYWALL: + if (--group->val2 <= 0) { + skill_delunitgroup(group); + break; + } +#ifdef RENEWAL + if (group->val3 - damage > 0) + group->val3 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + else + skill_delunitgroup(group); +#endif + break; + case MH_STEINWAND: + if (--group->val2 <= 0) { + skill_delunitgroup(group); + break; + } + if (group->val3 - damage > 0) + group->val3 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); + else + skill_delunitgroup(group); + break; + } + return false; + } + status_change_end(target, SC_SAFETYWALL); + } + + if ((sc->getSCE(SC_PNEUMA) && (flag&(BF_MAGIC | BF_LONG)) == BF_LONG) || +#ifdef RENEWAL + (sc->getSCE(SC_BASILICA_CELL) +#else + (sc->getSCE(SC_BASILICA) +#endif + && !status_bl_has_mode(src, MD_STATUSIMMUNE) && skill_id != SP_SOULEXPLOSION) || + (sc->getSCE(SC_ZEPHYR) && !(flag&BF_MAGIC && skill_id) && !(skill_get_inf(skill_id)&(INF_GROUND_SKILL | INF_SELF_SKILL))) || + sc->getSCE(SC__MANHOLE) || + sc->getSCE(SC_KINGS_GRACE) || + sc->getSCE(SC_GRAVITYCONTROL) + ) + { + d->dmg_lv = ATK_BLOCK; + return false; + } + + if (sc->getSCE(SC_WHITEIMPRISON)) { // Gravitation and Pressure do damage without removing the effect + if (skill_id == MG_NAPALMBEAT || + skill_id == MG_SOULSTRIKE || + skill_id == WL_SOULEXPANSION || + skill_id == AG_SOUL_VC_STRIKE || + (skill_id && skill_get_ele(skill_id, skill_lv) == ELE_GHOST) || + (skill_id == 0 && (status_get_status_data(*src))->rhw.ele == ELE_GHOST)) + { + if (skill_id == WL_SOULEXPANSION) + damage *= 2; // If used against a player in White Imprison, the skill deals double damage. + status_change_end(target, SC_WHITEIMPRISON); // Those skills do damage and removes effect + } else { + d->dmg_lv = ATK_BLOCK; + return false; + } + } + + if ((sce = sc->getSCE(SC_MILLENNIUMSHIELD)) && sce->val2 > 0 && damage > 0) { + sce->val3 -= static_cast(cap_value(damage, INT_MIN, INT_MAX)); // absorb damage + d->dmg_lv = ATK_BLOCK; + if (sce->val3 <= 0) { // Shield Down + sce->val2--; + if (sce->val2 > 0) { + clif_millenniumshield( *target, sce->val2 ); + sce->val3 = 1000; // Next shield + } else + status_change_end(target, SC_MILLENNIUMSHIELD); // All shields down + status_change_start(src, target, SC_STUN, 10000, 0, 0, 0, 0, 1000, SCSTART_NOTICKDEF); + } + return false; + } + + // ATK_MISS Type + if ((sce = sc->getSCE(SC_AUTOGUARD)) && flag&BF_WEAPON && rnd() % 100 < sce->val2 && !skill_get_inf2(skill_id, INF2_IGNOREAUTOGUARD)) { + status_change_entry *sce_d = sc->getSCE(SC_DEVOTION); + block_list *d_bl; + int delay; + + // 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; + + map_session_data *sd = map_id2sd(target->id); + + if (sd && pc_issit(sd)) + pc_setstand(sd, true); + if (sce_d && (d_bl = map_id2bl(sce_d->val1)) && + ((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_d->val2] == target->id)) && + check_distance_bl(target, d_bl, sce_d->val3)) + { //If player is target of devotion, show guard effect on the devotion caster rather than the target + clif_skill_nodamage(d_bl, d_bl, CR_AUTOGUARD, sce->val1, 1); + unit_set_walkdelay(d_bl, gettick(), delay, 1); + d->dmg_lv = ATK_MISS; + return false; + } else { + clif_skill_nodamage(target, target, CR_AUTOGUARD, sce->val1, 1); + unit_set_walkdelay(target, gettick(), delay, 1); +#ifdef RENEWAL + if (sc->getSCE(SC_SHRINK)) + sc_start(target, src, SC_STUN, 50, skill_lv, skill_get_time2(skill_id, skill_lv)); +#else + if (sc->getSCE(SC_SHRINK) && rnd() % 100 < 5 * sce->val1) + skill_blown(target, src, skill_get_blewcount(CR_SHRINK, 1), -1, BLOWN_NONE); +#endif + d->dmg_lv = ATK_MISS; + return false; + } + } + + if (sc->getSCE(SC_NEUTRALBARRIER) && ((flag&(BF_LONG|BF_MAGIC)) == BF_LONG +#ifndef RENEWAL + || skill_id == CR_ACIDDEMONSTRATION +#endif + )) { + d->dmg_lv = ATK_MISS; + return false; + } + + // ATK_DEF Type + if ((sce = sc->getSCE(SC_LIGHTNINGWALK)) && !(flag & BF_MAGIC) && flag&BF_LONG && rnd() % 100 < sce->val1) { + uint8 dir = map_calc_dir(target, src->x, src->y); + + if (unit_movepos(target, src->x - dirx[dir], src->y - diry[dir], 1, 1)) { + clif_blown(target); + unit_setdir(target, dir); + } + d->dmg_lv = ATK_DEF; + status_change_end(target, SC_LIGHTNINGWALK); + return false; + } + + // Other + if ((sc->getSCE(SC_HERMODE) && flag&BF_MAGIC) || + (sc->getSCE(SC_TATAMIGAESHI) && (flag&(BF_MAGIC | BF_LONG)) == BF_LONG) || + (sc->getSCE(SC_MEIKYOUSISUI) && rnd() % 100 < 40)) // custom value + return false; + + if ((sce = sc->getSCE(SC_PARRYING)) && flag&BF_WEAPON && skill_id != WS_CARTTERMINATION && rnd() % 100 < sce->val2) { + clif_skill_nodamage(target, target, LK_PARRYING, sce->val1, 1); + + if (skill_id == LK_PARRYING) { + unit_data *ud = unit_bl2ud(target); + + if (ud != nullptr) // Delay the next attack + ud->attackabletime = gettick() + status_get_adelay(target); + } + return false; + } + + if (sc->getSCE(SC_DODGE) && (flag&BF_LONG || sc->getSCE(SC_SPURT)) && (skill_id != NPC_EARTHQUAKE || (skill_id == NPC_EARTHQUAKE && flag & NPC_EARTHQUAKE_FLAG)) && rnd() % 100 < 20) { + map_session_data *sd = map_id2sd(target->id); + + if (sd && pc_issit(sd)) + pc_setstand(sd, true); //Stand it to dodge. + clif_skill_nodamage(target, target, TK_DODGE, 1, 1); + sc_start4(src, target, SC_COMBO, 100, TK_JUMPKICK, src->id, 1, 0, 2000); + return false; + } + + if ((sce = sc->getSCE(SC_KAUPE)) && (skill_id != NPC_EARTHQUAKE || (skill_id == NPC_EARTHQUAKE && flag & NPC_EARTHQUAKE_FLAG)) && rnd() % 100 < sce->val2) { //Kaupe blocks damage (skill or otherwise) from players, mobs, homuns, mercenaries. + clif_specialeffect(target, EF_STORMKICK4, AREA); + //Shouldn't end until Breaker's non-weapon part connects. +#ifndef RENEWAL + if (skill_id != ASC_BREAKER || !(flag&BF_WEAPON)) +#endif + if (--sce->val3 <= 0) //We make it work like Safety Wall, even though it only blocks 1 time. + status_change_end(target, SC_KAUPE); + return false; + } + + if (flag&BF_MAGIC && (sce = sc->getSCE(SC_PRESTIGE)) && rnd() % 100 < sce->val2) { + clif_specialeffect(target, EF_STORMKICK4, AREA); // Still need confirm it. + return false; + } + + if (((sce = sc->getSCE(SC_UTSUSEMI)) || sc->getSCE(SC_BUNSINJYUTSU)) && flag&BF_WEAPON && !skill_get_inf2(skill_id, INF2_IGNORECICADA)) { + skill_additional_effect(src, target, skill_id, skill_lv, flag, ATK_BLOCK, gettick()); + if (!status_isdead(*src)) + skill_counter_additional_effect(src, target, skill_id, skill_lv, flag, gettick()); + if (sce) { + clif_specialeffect(target, EF_STORMKICK4, AREA); + skill_blown(src, target, sce->val3, -1, BLOWN_NONE); + } + //Both need to be consumed if they are active. + if (sce && --sce->val2 <= 0) + status_change_end(target, SC_UTSUSEMI); + if ((sce = sc->getSCE(SC_BUNSINJYUTSU)) && --sce->val2 <= 0) + status_change_end(target, SC_BUNSINJYUTSU); + return false; + } + + if ((sce = sc->getSCE(SC_DAMAGE_HEAL))) { + if (damage > 0 && (flag & sce->val2)) { + int32 heal = static_cast( i64min( damage, INT32_MAX ) ); + if(flag & BF_WEAPON) { + clif_specialeffect_value(target, EF_HEAL, heal, AREA); + } else { + clif_specialeffect_value(target, 1143, heal, AREA); + } + clif_specialeffect_value(target, EF_GREEN_NUMBER, heal, AREA); + status_heal(target, damage, 0, 0); + damage = 0; + return false; + } + } + + return true; +} + +/** + * Check damage through status. + * ATK may be MISS, BLOCKED FAIL, reduc, increase, end status. + * After this we apply bg/gvg reduction + * @param src + * @param bl + * @param d + * @param damage + * @param skill_id + * @param skill_lv + * @return damage + */ +int64 battle_calc_damage(struct block_list *src,struct block_list *bl,struct Damage *d,int64 damage,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = nullptr, *tsd = BL_CAST(BL_PC, src); + 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( map_getcell(bl->m, bl->x, bl->y, CELL_CHKMAELSTROM) && skill_id && skill_get_type(skill_id) != BF_MISC + && skill_get_casttype(skill_id) == CAST_GROUND ) + return 0; + + if (bl->type == BL_PC) { + sd=(map_session_data *)bl; + //Special no damage states + if(flag&BF_WEAPON && sd->special_state.no_weapon_damage) + damage -= damage * sd->special_state.no_weapon_damage / 100; + + if(flag&BF_MAGIC && sd->special_state.no_magic_damage) + damage -= damage * sd->special_state.no_magic_damage / 100; + + if(flag&BF_MISC && sd->special_state.no_misc_damage) + damage -= damage * sd->special_state.no_misc_damage / 100; + + if(!damage) + return 0; + } + + switch (skill_id) { +#ifndef RENEWAL + case PA_PRESSURE: + case HW_GRAVITATION: +#endif + case SP_SOULEXPLOSION: + // Adjust these based on any possible PK damage rates. + if (battle_config.pk_mode == 1 && map_getmapflag(bl->m, MF_PVP) > 0) + damage = battle_calc_pk_damage(*src, *bl, damage, skill_id, flag); + + return damage; //These skills bypass everything else. + } + + status_change* tsc = status_get_sc(bl); //check target status + + // Nothing can reduce the damage, but Safety Wall and Millennium Shield can block it completely. + // So can defense sphere's but what the heck is that??? [Rytech] + if (skill_id == SJ_NOVAEXPLOSING && !(tsc && (tsc->getSCE(SC_SAFETYWALL) || tsc->getSCE(SC_MILLENNIUMSHIELD)))) { + // Adjust this based on any possible PK damage rates. + if (battle_config.pk_mode == 1 && map_getmapflag(bl->m, MF_PVP) > 0) + damage = battle_calc_pk_damage(*src, *bl, damage, skill_id, flag); + + return damage; + } + + if( tsc && tsc->count ) { + // Damage increasing effects +#ifdef RENEWAL // Flat +400% damage from melee + if (tsc->getSCE(SC_KAITE) && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT) + damage *= 4; +#endif + + if (tsc->getSCE(SC_GROUNDGRAVITY) && flag&(BF_MAGIC|BF_WEAPON)) + damage += damage * 15 / 100; + if (tsc->getSCE(SC_SHIELDCHAINRUSH)) + damage += damage / 10; + + if (tsc->getSCE(SC_AETERNA) && skill_id != PF_SOULBURN) { + if (src->type != BL_MER || !skill_id) + damage *= 2; // Lex Aeterna only doubles damage of regular attacks from mercenaries + +#ifndef RENEWAL + if( skill_id != ASC_BREAKER || !(flag&BF_WEAPON) ) +#endif + status_change_end(bl, SC_AETERNA); //Shouldn't end until Breaker's non-weapon part connects. + } + +#ifdef RENEWAL + if( tsc->getSCE(SC_RAID) ) { + if (status_get_class_(bl) == CLASS_BOSS) + damage += damage * 15 / 100; + else + damage += damage * 30 / 100; + } +#endif + + if( damage ) { + if( tsc->getSCE(SC_DEEPSLEEP) ) { + damage += damage / 2; // 1.5 times more damage while in Deep Sleep. + status_change_end(bl,SC_DEEPSLEEP); + } + if( tsd && sd && tsc->getSCE(SC_CRYSTALIZE) && flag&BF_WEAPON ) { + switch(tsd->status.weapon) { + case W_MACE: + case W_2HMACE: + case W_1HAXE: + case W_2HAXE: + damage += damage / 2; + break; + case W_MUSICAL: + case W_WHIP: + if(!tsd->state.arrow_atk) + break; + [[fallthrough]]; + 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 -= damage / 2; + break; + } + } + if( tsc->getSCE(SC_VOICEOFSIREN) ) + status_change_end(bl,SC_VOICEOFSIREN); + } + + if (tsc->getSCE(SC_SOUNDOFDESTRUCTION)) + damage *= 2; + if (tsc->getSCE(SC_DARKCROW) && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT) { + int bonus = tsc->getSCE(SC_DARKCROW)->val2; + if( tsc->getSCE(SC_BURNT) && status_get_element(src) == ELE_FIRE ) + damage += damage * 666 / 100; //Custom value + + if (status_get_class_(bl) == CLASS_BOSS) + bonus /= 2; + + damage += damage * bonus / 100; + } + if (tsc->getSCE(SC_HOLY_OIL) && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + damage += damage * (3 * tsc->getSCE(SC_HOLY_OIL)->val1) / 100; + + if( tsc->getSCE( SC_RUSH_QUAKE1 ) && ( flag&BF_WEAPON ) == BF_WEAPON ){ + damage += damage * 50 / 100; + } + + if (tsc->getSCE(SC_SHADOW_SCAR)) // !TODO: Need official adjustment for this too. + damage += damage * (3 * tsc->getSCE(SC_SHADOW_SCAR)->val1) / 100; + + // Damage reductions + // Assumptio increases DEF on RE mode, otherwise gives a reduction on the final damage. [Igniz] +#ifndef RENEWAL + if( tsc->getSCE(SC_ASSUMPTIO) ) { + if( map_flag_vs(bl->m) ) + damage = (int64)damage*2/3; //Receive 66% damage + else + damage /= 2; //Receive 50% damage + } +#endif + + if (tsc->getSCE(SC_DEFENDER) && + skill_id != NJ_ZENYNAGE && skill_id != KO_MUCHANAGE && +#ifdef RENEWAL + ((flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON) || skill_id == GN_FIRE_EXPANSION_ACID)) +#else + (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) +#endif + damage -= damage * tsc->getSCE(SC_DEFENDER)->val2 / 100; + + if(tsc->getSCE(SC_ADJUSTMENT) && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + damage -= damage * 20 / 100; + + if(tsc->getSCE(SC_FOGWALL) && skill_id != RK_DRAGONBREATH && skill_id != RK_DRAGONBREATH_WATER && skill_id != NPC_DRAGONBREATH) { + if(flag&BF_SKILL) //25% reduction + damage -= damage * 25 / 100; + else if ((flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + damage /= 4; //75% reduction + } + + if (tsc->getSCE(SC_SPORE_EXPLOSION) && (flag & BF_LONG) == BF_LONG) + damage += damage * (status_get_class(bl) == CLASS_BOSS ? 5 : 10) / 100; + + if(tsc->getSCE(SC_ARMORCHANGE)) { + //On official servers, SC_ARMORCHANGE does not change DEF/MDEF but rather increases/decreases the damage + if(flag&BF_WEAPON) + damage -= damage * tsc->getSCE(SC_ARMORCHANGE)->val2 / 100; + else if(flag&BF_MAGIC) + damage -= damage * tsc->getSCE(SC_ARMORCHANGE)->val3 / 100; + } + + if(tsc->getSCE(SC_SMOKEPOWDER)) { + if( (flag&(BF_SHORT|BF_WEAPON)) == (BF_SHORT|BF_WEAPON) ) + damage -= damage * 15 / 100; // 15% reduction to physical melee attacks + else if( (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON) ) + damage -= damage * 50 / 100; // 50% reduction to physical ranged attacks + } + + if (tsc->getSCE(SC_WATER_BARRIER)) + damage = damage * 80 / 100; // 20% reduction to all type attacks + + if (tsc->getSCE(SC_SU_STOOP)) + damage -= damage * 90 / 100; + + // Compressed code, fixed by map.hpp [Epoque] + if (src->type == BL_MOB) { + std::vector race2 = status_get_race2(src); + + for (const auto &raceit : race2) { + switch (raceit) { + case RC2_MANUK: + if (sce = tsc->getSCE(SC_MANU_DEF)) + damage -= damage * sce->val1 / 100; + break; + case RC2_SPLENDIDE: + if (sce = tsc->getSCE(SC_SPL_DEF)) + damage -= damage * sce->val1 / 100; + break; + case RC2_OGH_ATK_DEF: + if (tsc->getSCE(SC_GLASTHEIM_DEF)) + return 0; + break; + case RC2_OGH_HIDDEN: + if (sce = tsc->getSCE(SC_GLASTHEIM_HIDDEN)) + damage -= damage * sce->val1 / 100; + break; + case RC2_BIO5_ACOLYTE_MERCHANT: + if (sce = tsc->getSCE(SC_LHZ_DUN_N1)) + damage -= damage * sce->val2 / 100; + break; + case RC2_BIO5_MAGE_ARCHER: + if (sce = tsc->getSCE(SC_LHZ_DUN_N2)) + damage -= damage * sce->val2 / 100; + break; + case RC2_BIO5_SWORDMAN_THIEF: + if (sce = tsc->getSCE(SC_LHZ_DUN_N3)) + damage -= damage * sce->val2 / 100; + break; + case RC2_BIO5_MVP: + if (sce = tsc->getSCE(SC_LHZ_DUN_N4)) + damage -= damage * sce->val2 / 100; + break; + } + } + } + + if((sce=tsc->getSCE(SC_ARMOR)) && //NPC_DEFENDER + sce->val3&flag && sce->val4&flag) + damage /= tsc->getSCE(SC_ARMOR)->val2; + + if( tsc->getSCE(SC_ENERGYCOAT) && (skill_id == GN_HELLS_PLANT_ATK || +#ifdef RENEWAL + ((flag&BF_WEAPON || flag&BF_MAGIC) && skill_id != WS_CARTTERMINATION) +#else + (flag&BF_WEAPON && skill_id != WS_CARTTERMINATION) +#endif + ) ) + { + 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); + damage -= damage * 6 * (1 + per) / 100; //Reduction: 6% + 6% every 20% + } + + if(tsc->getSCE(SC_GRANITIC_ARMOR)) + damage -= damage * tsc->getSCE(SC_GRANITIC_ARMOR)->val2 / 100; + + if(tsc->getSCE(SC_PAIN_KILLER)) { + damage -= tsc->getSCE(SC_PAIN_KILLER)->val2; + damage = i64max(damage, 1); + } + + if( (sce=tsc->getSCE(SC_MAGMA_FLOW)) && (rnd()%100 <= sce->val2) ) + skill_castend_damage_id(bl,src,MH_MAGMA_FLOW,sce->val1,gettick(),0); + + if( damage > 0 && (sce = tsc->getSCE(SC_STONEHARDSKIN)) ) { + if( src->type == BL_MOB ) //using explicit call instead break_equip for duration + sc_start(src,src, SC_STRIPWEAPON, 30, 0, skill_get_time2(RK_STONEHARDSKIN, sce->val1)); + else if (flag&(BF_WEAPON|BF_SHORT)) + skill_break_equip(src,src, EQP_WEAPON, 3000, BCT_SELF); + } + + if (src->type == BL_PC && tsc->getSCE(SC_GVG_GOLEM)) { + if (flag&BF_WEAPON) + damage -= damage * tsc->getSCE(SC_GVG_GOLEM)->val3 / 100; + if (flag&BF_MAGIC) + damage -= damage * tsc->getSCE(SC_GVG_GOLEM)->val4 / 100; + } + +#ifdef RENEWAL + // Renewal: steel body reduces all incoming damage to 1/10 [helvetica] + if( tsc->getSCE(SC_STEELBODY) ) + damage = damage > 10 ? damage / 10 : 1; +#endif + + //Finally added to remove the status of immobile when Aimed Bolt is used. [Jobbie] + if( skill_id == RA_AIMEDBOLT && (tsc->getSCE(SC_BITE) || tsc->getSCE(SC_ANKLE) || tsc->getSCE(SC_ELECTRICSHOCKER)) ) { + status_change_end(bl, SC_BITE); + status_change_end(bl, SC_ANKLE); + status_change_end(bl, SC_ELECTRICSHOCKER); + } + + if (!damage) + return 0; + + if( sd && (sce = tsc->getSCE(SC_FORCEOFVANGUARD)) && flag&BF_WEAPON && rnd()%100 < sce->val2 ) + pc_addspiritball(sd,skill_get_time(LG_FORCEOFVANGUARD,sce->val1),sce->val3); + + if( sd && (sce = tsc->getSCE(SC_GT_ENERGYGAIN)) && flag&BF_WEAPON && rnd()%100 < sce->val2 ) { + int spheres = 5; + + if( tsc->getSCE(SC_RAISINGDRAGON) ) + spheres += tsc->getSCE(SC_RAISINGDRAGON)->val1; + + pc_addspiritball(sd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, sce->val1), spheres); + } + + if (tsc->getSCE(SC_STYLE_CHANGE) && tsc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_GRAPPLING) { + TBL_HOM *hd = BL_CAST(BL_HOM,bl); // We add a sphere for when the Homunculus is being hit + + if (hd && (rnd()%100<50) ) // According to WarpPortal, this is a flat 50% chance + hom_addspiritball(hd, 10); + } + + if( tsc->getSCE(SC__DEADLYINFECT) && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT && damage > 0 && rnd()%100 < 30 + 10 * tsc->getSCE(SC__DEADLYINFECT)->val1 ) + status_change_spread(bl, src); + + } //End of target SC_ check + + //SC effects from caster side. + status_change* sc = status_get_sc(src); + + if (sc && sc->count) { + if (sc->getSCE(SC_BREAKINGLIMIT)) { + switch (skill_id) { + case HN_SHIELD_CHAIN_RUSH: + case HN_DOUBLEBOWLINGBASH: + damage += damage * 120 / 100; + break; + case HN_MEGA_SONIC_BLOW: + case HN_SPIRAL_PIERCE_MAX: + damage *= 2; + break; + } + } + + if (sc->getSCE(SC_RULEBREAK)) { + switch (skill_id) { + case HN_METEOR_STORM_BUSTER: + case HN_GROUND_GRAVITATION: + damage += damage / 2; + break; + case HN_JUPITEL_THUNDER_STORM: + case HN_JACK_FROST_NOVA: + case HN_HELLS_DRIVE: + damage += damage * 70 / 100; + break; + case HN_NAPALM_VULCAN_STRIKE: + damage += damage * 40 / 100; + break; + } + } + + if ((sce = sc->getSCE(SC_BLOODLUST)) && flag & BF_WEAPON && damage > 0 && rnd_chance(sce->val3, 100)) + status_heal(src, damage * sce->val4 / 100, 0, 1); + + if ((sce = sc->getSCE(SC_BLOODSUCKER)) && flag & BF_WEAPON && damage > 0 && rnd() % 100 < (2 * sce->val1 - 1)) + status_heal(src, damage * sce->val1 / 100, 0, 3); + + if (flag&BF_MAGIC && bl->type == BL_PC && sc->getSCE(SC_GVG_GIANT) && sc->getSCE(SC_GVG_GIANT)->val4) + damage += damage * sc->getSCE(SC_GVG_GIANT)->val4 / 100; + + // [Epoque] + if (bl->type == BL_MOB) { + if ((flag&BF_WEAPON) || (flag&BF_MAGIC)) { + std::vector race2 = status_get_race2(bl); + + for (const auto &raceit : race2) { + switch (raceit) { + case RC2_MANUK: + if (sce = sc->getSCE(SC_MANU_ATK)) + damage += damage * sce->val1 / 100; + break; + case RC2_SPLENDIDE: + if (sce = sc->getSCE(SC_SPL_ATK)) + damage += damage * sce->val1 / 100; + break; + case RC2_OGH_ATK_DEF: + if (sc->getSCE(SC_GLASTHEIM_ATK)) + damage *= 2; + break; + case RC2_BIO5_SWORDMAN_THIEF: + if (sce = sc->getSCE(SC_LHZ_DUN_N1)) + damage += damage * sce->val1 / 100; + break; + case RC2_BIO5_ACOLYTE_MERCHANT: + if (sce = sc->getSCE(SC_LHZ_DUN_N2)) + damage += damage * sce->val1 / 100; + break; + case RC2_BIO5_MAGE_ARCHER: + if (sce = sc->getSCE(SC_LHZ_DUN_N3)) + damage += damage * sce->val1 / 100; + break; + case RC2_BIO5_MVP: + if (sce = sc->getSCE(SC_LHZ_DUN_N4)) + damage += damage * sce->val1 / 100; + break; + } + } + } + } + + if (sc->getSCE(SC_POISONINGWEAPON) && flag&BF_SHORT && damage > 0) { + damage += damage * 10 / 100; + if (rnd() % 100 < sc->getSCE(SC_POISONINGWEAPON)->val3) + sc_start4(src, bl, (sc_type)sc->getSCE(SC_POISONINGWEAPON)->val2, 100, sc->getSCE(SC_POISONINGWEAPON)->val1, 0, 1, 0, (sc->getSCE(SC_POISONINGWEAPON)->val2 == SC_VENOMBLEED ? skill_get_time2(GC_POISONINGWEAPON, 1) : skill_get_time2(GC_POISONINGWEAPON, 2))); + } + + if( sc->getSCE(SC__DEADLYINFECT) && (flag&(BF_SHORT|BF_MAGIC)) == BF_SHORT && damage > 0 && rnd()%100 < 30 + 10 * sc->getSCE(SC__DEADLYINFECT)->val1 ) + status_change_spread(src, bl); + + if (sc->getSCE(SC_STYLE_CHANGE) && sc->getSCE(SC_STYLE_CHANGE)->val1 == MH_MD_FIGHTING) { + TBL_HOM *hd = BL_CAST(BL_HOM,src); //when attacking + + if (hd && (rnd()%100<50) ) hom_addspiritball(hd, 10); // According to WarpPortal, this is a flat 50% chance + } + + if (flag & BF_WEAPON && (sce = sc->getSCE(SC_ADD_ATK_DAMAGE))) + damage += damage * sce->val1 / 100; + if (flag & BF_MAGIC && (sce = sc->getSCE(SC_ADD_MATK_DAMAGE))) + damage += damage * sce->val1 / 100; + if (sc->getSCE(SC_DANCEWITHWUG) && (flag&(BF_LONG|BF_WEAPON)) == (BF_LONG|BF_WEAPON)) + damage += damage * sc->getSCE(SC_DANCEWITHWUG)->val1 / 100; + if (sc->getSCE(SC_UNLIMITEDHUMMINGVOICE) && flag&BF_MAGIC) + damage += damage * sc->getSCE(SC_UNLIMITEDHUMMINGVOICE)->val3 / 100; + + if (tsd && (sce = sc->getSCE(SC_SOULREAPER))) { + if (rnd()%100 < sce->val2 && tsd->soulball < MAX_SOUL_BALL) { + clif_specialeffect(src, 1208, AREA); + pc_addsoulball(tsd, 5 + 3 * pc_checkskill(tsd, SP_SOULENERGY)); + } + } + } //End of caster SC_ check + + //PK damage rates + if (battle_config.pk_mode == 1 && map_getmapflag(bl->m, MF_PVP) > 0) + damage = battle_calc_pk_damage(*src, *bl, damage, skill_id, flag); + + 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 (sd && pc_ismadogear(sd)) { + pc_overheat(*sd, (battle_get_weapon_element(d, src, bl, skill_id, skill_lv, EQI_HAND_R, false) == ELE_FIRE ? 3 : 1)); + } + + // Target status (again), required for RELIEVE + sc = status_get_sc(bl); + + // !TODO: Should RELIEVE needed to be down here or after some other check? Should the skill be independent of damagetaken from mob_db.yml? + if (sc && sc->count) { + if ((sce = sc->getSCE(SC_RELIEVE_ON)) && !sc->getSCE(SC_RELIEVE_OFF)) + damage = i64max((damage - damage * sce->val2 / 100), 1); + } + + if (bl->type == BL_MOB) { // Reduces damage received for Green Aura MVP + mob_data *md = BL_CAST(BL_MOB, bl); + + if (md && md->damagetaken != 100) + damage = i64max(damage * md->damagetaken / 100, 1); + } + + if (tsc && tsc->count) { + if (!battle_status_block_damage(src, bl, tsc, d, damage, skill_id, skill_lv)) // Statuses that reduce damage to 0. + return 0; + } + + return damage; +} + +/** + * Determines whether battleground target can be hit + * @param src: Source of attack + * @param bl: Target of attack + * @param skill_id: Skill ID used + * @param flag: Special flags + * @return Can be hit (true) or can't be hit (false) + */ +bool battle_can_hit_bg_target(struct block_list *src, struct block_list *bl, uint16 skill_id, int flag) +{ + struct mob_data* md = BL_CAST(BL_MOB, bl); + struct unit_data *ud = unit_bl2ud(bl); + + if (ud && ud->immune_attack) + return false; + if (md && md->bg_id) { + if (status_bl_has_mode(bl, MD_SKILLIMMUNE) && flag&BF_SKILL) //Skill immunity. + return false; + if (src->type == BL_PC) { + map_session_data *sd = map_id2sd(src->id); + + if (sd && sd->bg_id == md->bg_id) + return false; + } + } + return true; +} + +/** + * Calculates BG related damage adjustments. + * @param src + * @param bl + * @param damage + * @param skill_id + * @param flag + * @return damage + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +int64 battle_calc_bg_damage(struct block_list *src, struct block_list *bl, int64 damage, uint16 skill_id, int flag) +{ + if( !damage ) + return 0; + + if (!battle_can_hit_bg_target(src, bl, skill_id, flag)) + return 0; + + if(skill_get_inf2(skill_id, INF2_IGNOREBGREDUCTION)) + return damage; //skill that ignore bg map reduction + + if( flag&BF_SKILL ) { //Skills get a different reduction than non-skills. [Skotlex] + if( flag&BF_WEAPON ) + damage = damage * battle_config.bg_weapon_damage_rate / 100; + if( flag&BF_MAGIC ) + damage = damage * battle_config.bg_magic_damage_rate / 100; + if( flag&BF_MISC ) + damage = damage * battle_config.bg_misc_damage_rate / 100; + } else { //Normal attacks get reductions based on range. + if( flag&BF_SHORT ) + damage = damage * battle_config.bg_short_damage_rate / 100; + if( flag&BF_LONG ) + damage = damage * battle_config.bg_long_damage_rate / 100; + } + + damage = i64max(damage,1); //min 1 damage + return damage; +} + +/** + * Determines whether target can be hit + * @param src + * @param bl + * @param skill_id + * @param flag + * @return Can be hit (true) or can't be hit (false) + */ +bool battle_can_hit_gvg_target(struct block_list *src,struct block_list *bl,uint16 skill_id,int flag) +{ + struct mob_data* md = BL_CAST(BL_MOB, bl); + struct unit_data *ud = unit_bl2ud(bl); + int class_ = status_get_class(bl); + + if (ud && ud->immune_attack) + return false; + if(md && (md->guardian_data || md->special_state.ai == AI_GUILD)) { + if ((status_bl_has_mode(bl,MD_SKILLIMMUNE) || (class_ == MOBID_EMPERIUM && !skill_get_inf2(skill_id, INF2_TARGETEMPERIUM))) && flag&BF_SKILL) //Skill immunity. + return false; + if( src->type != BL_MOB || mob_is_clone( ((struct mob_data*)src)->mob_id ) ){ + auto g = src->type == BL_PC ? ((TBL_PC *)src)->guild : guild_search(status_get_guild_id(src)); + + if (class_ == MOBID_EMPERIUM && (!g || guild_checkskill(g->guild, GD_APPROVAL) <= 0 )) + return false; + + if (g != nullptr) { + if (battle_config.guild_max_castles && guild_checkcastles(g->guild)>=battle_config.guild_max_castles) + return false; // [MouseJstr] + + if (md->special_state.ai == AI_GUILD && g->guild.guild_id == md->master_id) + return false; + } + } + } + return true; +} + +/** + * Calculates GVG related damage adjustments. + * @param src + * @param bl + * @param damage + * @param skill_id + * @param flag + * @return damage + */ +int64 battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag) +{ + if (!damage) //No reductions to make. + return 0; + + if (!battle_can_hit_gvg_target(src,bl,skill_id,flag)) + return 0; + + if (skill_get_inf2(skill_id, INF2_IGNOREGVGREDUCTION)) //Skills with no gvg damage reduction. + return damage; + + if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex] + if (flag&BF_WEAPON) + damage = damage * battle_config.gvg_weapon_damage_rate / 100; + if (flag&BF_MAGIC) + damage = damage * battle_config.gvg_magic_damage_rate / 100; + if (flag&BF_MISC) + damage = damage * battle_config.gvg_misc_damage_rate / 100; + } else { //Normal attacks get reductions based on range. + if (flag & BF_SHORT) + damage = damage * battle_config.gvg_short_damage_rate / 100; + if (flag & BF_LONG) + damage = damage * battle_config.gvg_long_damage_rate / 100; + } + damage = i64max(damage,1); + return damage; +} + +/** + * Calculates PK related damage adjustments (between players only). + * @param src: Source object + * @param bl: Target object + * @param damage: Damage being done + * @param skill_id: Skill used + * @param flag: Battle flag type + * @return Modified damage + */ +int64 battle_calc_pk_damage(block_list &src, block_list &bl, int64 damage, uint16 skill_id, int flag) { + if (damage == 0) // No reductions to make. + return 0; + + if (battle_config.pk_mode == 0) // PK mode is disabled. + return damage; + + if (src.type == BL_PC && bl.type == BL_PC) { + if (flag & BF_SKILL) { //Skills get a different reduction than non-skills. [Skotlex] + if (flag & BF_WEAPON) + damage = damage * battle_config.pk_weapon_damage_rate / 100; + if (flag & BF_MAGIC) + damage = damage * battle_config.pk_magic_damage_rate / 100; + if (flag & BF_MISC) + damage = damage * battle_config.pk_misc_damage_rate / 100; + } else { //Normal attacks get reductions based on range. + if (flag & BF_SHORT) + damage = damage * battle_config.pk_short_damage_rate / 100; + if (flag & BF_LONG) + damage = damage * battle_config.pk_long_damage_rate / 100; + } + } + + return i64max(damage, 1); +} + +/** + * HP/SP drain calculation + * @param damage Damage inflicted to the enemy + * @param rate Success chance 1000 = 100% + * @param per HP/SP drained + * @return diff + */ +static int battle_calc_drain(int64 damage, int rate, int per) +{ + int64 diff = 0; + + if (per && (rate > 1000 || rnd()%1000 < rate)) { + diff = (damage * per) / 100; + if (diff == 0) { + if (per > 0) + diff = 1; + else + diff = -1; + } + } + return (int)cap_value(diff, INT_MIN, INT_MAX); +} + +/** + * Passive skill damage increases + * @param sd + * @param target + * @param dmg + * @param type + * @return damage + */ +int64 battle_addmastery(map_session_data *sd,struct block_list *target,int64 dmg,int type) +{ + int64 damage; + status_data* status = status_get_status_data(*target); + int weapon, skill; + +#ifdef RENEWAL + damage = 0; +#else + damage = dmg; +#endif + + nullpo_ret(sd); + + if((skill = pc_checkskill(sd,AL_DEMONBANE)) > 0 && + target->type == BL_MOB && //This bonus doesn't 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 + if( (skill = pc_checkskill(sd, RA_RANGERMAIN)) > 0 && (status->race == RC_BRUTE || status->race == RC_PLAYER_DORAM || 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); + + damage += (15 * pc_checkskill(sd, NC_MADOLICENCE)); // Attack bonus is granted even without the Madogear + + if((skill = pc_checkskill(sd,HT_BEASTBANE)) > 0 && (status->race == RC_INSECT || status->race == RC_BRUTE || status->race == RC_PLAYER_DORAM) ) { + damage += (skill * 4); + if (sd->sc.getSCE(SC_SPIRIT) && sd->sc.getSCE(SC_SPIRIT)->val2 == SL_HUNTER) + damage += sd->status.str; + } + +#ifdef RENEWAL + //Weapon Research bonus applies to all weapons + if((skill = pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0) + damage += (skill * 2); +#endif + + if ((skill = pc_checkskill(sd, NV_BREAKTHROUGH)) > 0) + damage += 15 * skill + (skill > 4 ? 25 : 0); + + // Kagerou/Oboro Spirit Charm bonus + if (sd->spiritcharm >= MAX_SPIRITCHARM) { + if ((sd->spiritcharm_type == CHARM_TYPE_FIRE && status->def_ele == ELE_EARTH) || + (sd->spiritcharm_type == CHARM_TYPE_WATER && status->def_ele == ELE_FIRE) || + (sd->spiritcharm_type == CHARM_TYPE_LAND && status->def_ele == ELE_WIND) || + (sd->spiritcharm_type == CHARM_TYPE_WIND && status->def_ele == ELE_WATER)) + damage += damage * 30 / 100; + } + + 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: + 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) && !pc_isridingdragon(sd)) + damage += (skill * 4); + else + damage += (skill * 5); + // Increase damage by level of KN_SPEARMASTERY * 10 + if(pc_checkskill(sd,RK_DRAGONTRAINING) > 0) + damage += (skill * 10); + } + 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 * 4); + break; + case W_FIST: + if((skill = pc_checkskill(sd,TK_RUN)) > 0) + damage += (skill * 10); + [[fallthrough]]; + 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 overrefine damage bonus and weapon related bonuses (unofficial) +* @param sd Player +* @param damage Current damage +* @param lr_type EQI_HAND_L:left-hand weapon, EQI_HAND_R:right-hand weapon +*/ +static void battle_add_weapon_damage(map_session_data *sd, int64 *damage, int lr_type) { + if (!sd) + return; + //rodatazone says that Overrefine bonuses are part of baseatk + //Here we also apply the weapon_damage_rate bonus so it is correctly applied on left/right hands. + if (lr_type == EQI_HAND_L) { + if (sd->left_weapon.overrefine) + (*damage) = (*damage) + rnd() % sd->left_weapon.overrefine + 1; + if (sd->indexed_bonus.weapon_damage_rate[sd->weapontype2]) + (*damage) += (*damage) * sd->indexed_bonus.weapon_damage_rate[sd->weapontype2] / 100; + } + else if (lr_type == EQI_HAND_R) { + if (sd->right_weapon.overrefine) + (*damage) = (*damage) + rnd() % sd->right_weapon.overrefine + 1; + if (sd->indexed_bonus.weapon_damage_rate[sd->weapontype1]) + (*damage) += (*damage) * sd->indexed_bonus.weapon_damage_rate[sd->weapontype1] / 100; + } +} + +#ifdef RENEWAL +static int battle_calc_sizefix(int64 damage, map_session_data *sd, unsigned char t_size, unsigned char weapon_type, short flag) +{ + if (sd && !sd->special_state.no_sizefix && !flag) // Size fix only for players + damage = damage * (weapon_type == EQI_HAND_L ? sd->left_weapon.atkmods[t_size] : sd->right_weapon.atkmods[t_size]) / 100; + + return (int)cap_value(damage, INT_MIN, INT_MAX); +} + +/** + * Calculates renewal Variance, OverUpgradeBonus, and SizePenaltyMultiplier of weapon damage parts for player + * @param src Block list of attacker + * @param tstatus Target's status data + * @param wa Weapon attack data + * @param sd Player + * @return Base weapon damage + */ +static int battle_calc_base_weapon_attack(struct block_list *src, struct status_data *tstatus, struct weapon_atk *wa, map_session_data *sd, bool critical) +{ + status_data* status = status_get_status_data(*src); + uint8 type = (wa == &status->lhw)?EQI_HAND_L:EQI_HAND_R; + uint16 atkmin = (type == EQI_HAND_L)?status->watk2:status->watk; + uint16 atkmax = atkmin; + int64 damage = atkmin; + bool weapon_perfection = false; + status_change *sc = status_get_sc(src); + + if (sd && sd->equip_index[type] >= 0 && sd->inventory_data[sd->equip_index[type]]) { + short base_stat; + + switch (sd->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: + if (pc_checkskill(sd, SU_SOULATTACK) > 0) + base_stat = status->str; + else + base_stat = status->dex; + break; + default: + base_stat = status->str; + break; + } + + float variance = 5.0f * wa->atk * wa->wlv / 100.0f; + float base_stat_bonus = wa->atk * base_stat / 200.0f; + + atkmin = max(0, (int)(atkmin - variance + base_stat_bonus)); + atkmax = min(UINT16_MAX, (int)(atkmax + variance + base_stat_bonus)); + + if ((sc && sc->getSCE(SC_MAXIMIZEPOWER)) || critical == true) + damage = atkmax; + else + damage = rnd_value(atkmin, atkmax); + } + + if (sc && sc->getSCE(SC_WEAPONPERFECTION)) + weapon_perfection = true; + + battle_add_weapon_damage(sd, &damage, type); + + damage = battle_calc_sizefix(damage, sd, tstatus->size, type, weapon_perfection); + + return (int)cap_value(damage, INT_MIN, INT_MAX); +} +#endif + +/*========================================== + * Calculates the standard damage of a normal attack assuming it hits + * This applies to pre-renewal and non-sd in renewal + *------------------------------------------ + * Pass damage2 as nullptr to not calc it. + * Flag values (see e_base_damage_flag): + * &1 : Critical hit + * &2 : Arrow attack + * &4 : Skill is Magic Crasher + * &8 : Skip target size adjustment (Weapon Perfection) + * &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) + * + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static int64 battle_calc_base_damage(struct block_list *src, struct status_data *status, struct weapon_atk *wa, status_change *sc, unsigned short t_size, int flag) +{ + unsigned int atkmin = 0, atkmax = 0; + short type = 0; + int64 damage = 0; + map_session_data *sd = nullptr; + + nullpo_retr(damage, src); + + sd = BL_CAST(BL_PC, src); + + if (!sd) { //Mobs/Pets +#ifndef RENEWAL + if (sc != nullptr && sc->getSCE(SC_CHANGE) != nullptr) + return status->matk_max; // [Aegis] simply uses raw max matk for base damage when Mental Charge active +#endif + if(flag&BDMG_MAGIC) { + 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&BDMG_CRIT) || (flag&BDMG_ARROW)) { //Normal attacks + atkmin = status->dex; + + if (sd->equip_index[type] >= 0 && sd->inventory_data[sd->equip_index[type]] && sd->inventory_data[sd->equip_index[type]]->type == IT_WEAPON) + atkmin = atkmin*(80 + sd->inventory_data[sd->equip_index[type]]->weapon_level*20)/100; + + if (atkmin > atkmax) + atkmin = atkmax; + + if(flag&BDMG_ARROW) { //Bows + atkmin = atkmin*atkmax/100; + if (atkmin > atkmax) + atkmax = atkmin; + } + } + } + + if (sc && sc->getSCE(SC_MAXIMIZEPOWER)) + atkmin = atkmax; + + //Weapon Damage calculation + if (!(flag&BDMG_CRIT)) + 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&BDMG_ARROW && sd->bonus.arrow_atk) + damage += ( (flag&BDMG_CRIT) ? sd->bonus.arrow_atk : rnd()%sd->bonus.arrow_atk ); + + // Size fix only for players + if (!(sd->special_state.no_sizefix || (flag&BDMG_NOSIZE))) + damage = damage * (type == EQI_HAND_L ? sd->left_weapon.atkmods[t_size] : sd->right_weapon.atkmods[t_size]) / 100; + } else if (src->type == BL_ELEM) { + status_change *ele_sc = status_get_sc(src); + int ele_class = status_get_class(src); + + if (ele_sc) { + switch (ele_class) { + case ELEMENTALID_AGNI_S: + case ELEMENTALID_AGNI_M: + case ELEMENTALID_AGNI_L: + case ELEMENTALID_ARDOR: + if (ele_sc->getSCE(SC_FIRE_INSIGNIA) && ele_sc->getSCE(SC_FIRE_INSIGNIA)->val1 == 1) + damage += damage * 20 / 100; + break; + case ELEMENTALID_AQUA_S: + case ELEMENTALID_AQUA_M: + case ELEMENTALID_AQUA_L: + case ELEMENTALID_DILUVIO: + if (ele_sc->getSCE(SC_WATER_INSIGNIA) && ele_sc->getSCE(SC_WATER_INSIGNIA)->val1 == 1) + damage += damage * 20 / 100; + break; + case ELEMENTALID_VENTUS_S: + case ELEMENTALID_VENTUS_M: + case ELEMENTALID_VENTUS_L: + case ELEMENTALID_PROCELLA: + if (ele_sc->getSCE(SC_WIND_INSIGNIA) && ele_sc->getSCE(SC_WIND_INSIGNIA)->val1 == 1) + damage += damage * 20 / 100; + break; + case ELEMENTALID_TERA_S: + case ELEMENTALID_TERA_M: + case ELEMENTALID_TERA_L: + case ELEMENTALID_TERREMOTUS: + case ELEMENTALID_SERPENS: + if (ele_sc->getSCE(SC_EARTH_INSIGNIA) && ele_sc->getSCE(SC_EARTH_INSIGNIA)->val1 == 1) + damage += damage * 20 / 100; + break; + } + } + } + + //Finally, add baseatk + if(flag&BDMG_MAGIC) + damage += status->matk_min; + else + damage += status->batk; + + if (sd) + battle_add_weapon_damage(sd, &damage, type); + +#ifdef RENEWAL + if (flag&BDMG_CRIT) + damage = (damage * 14) / 10; +#endif + + return damage; +} + +/*========================================== + * Consumes ammo for the given skill. + *------------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +void battle_consume_ammo(map_session_data*sd, int skill, int lv) +{ + int qty = 1; + + if( sd == nullptr ){ + return; + } + + if (!battle_config.arrow_decrement) + return; + + if (skill) { + qty = skill_get_ammo_qty(skill, lv); + if (!qty) qty = 1; + + if( skill == NW_MAGAZINE_FOR_ONE && sd->weapontype1 == W_GATLING ){ + qty += 4; + } + } + + 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) +{ + // [Akinari] , [Xynvaroth]: Traps are always short range. + if (skill_get_inf2(skill_id, INF2_ISTRAP)) + return BF_SHORT; + + switch (skill_id) { + case AC_SHOWER: + case AM_DEMONSTRATION: + // When monsters use Arrow Shower or Bomb, it is always short range + if (src->type == BL_MOB) + return BF_SHORT; + break; +#ifdef RENEWAL + case KN_BRANDISHSPEAR: + // Renewal changes to ranged physical damage +#endif + case SR_RAMPAGEBLASTER: + case BO_ACIDIFIED_ZONE_WATER_ATK: + case BO_ACIDIFIED_ZONE_FIRE_ATK: + case BO_ACIDIFIED_ZONE_GROUND_ATK: + case BO_ACIDIFIED_ZONE_WIND_ATK: + case NW_THE_VIGILANTE_AT_NIGHT: + return BF_LONG; + case NJ_KIRIKAGE: // Cast range mimics NJ_SHADOWJUMP but damage is considered melee + case GC_CROSSIMPACT: // Cast range is 7 cells and player jumps to target but skill is considered melee + case DK_SERVANT_W_PHANTOM: // 9 cell cast range. + case SHC_SAVAGE_IMPACT: // 7 cell cast range. + case SHC_FATAL_SHADOW_CROW: // 9 cell cast range. + case MT_RUSH_QUAKE: // 9 cell cast range. + case ABC_UNLUCKY_RUSH: // 7 cell cast range. + case MH_THE_ONE_FIGHTER_RISES: // 7 cell cast range. + //case ABC_DEFT_STAB: // 2 cell cast range??? + case NPC_MAXPAIN_ATK: + return BF_SHORT; + case CD_PETITIO: { // Skill range is 2 but damage is melee with books and ranged with mace. + map_session_data *sd = BL_CAST(BL_PC, src); + + if (sd && (sd->status.weapon == W_MACE || sd->status.weapon == W_2HMACE)) + return BF_LONG; + + break; + } + case DK_HACKANDSLASHER: + case DK_HACKANDSLASHER_ATK: { + map_session_data* sd = BL_CAST( BL_PC, src ); + + if( sd != nullptr && ( sd->status.weapon == W_1HSPEAR || sd->status.weapon == W_2HSPEAR ) ){ + return BF_LONG; + } + + break; + } + } + + //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, 3)) + return BF_SHORT; + return BF_LONG; + } + + //based on used skill's range + if (skill_get_range2(src, skill_id, skill_lv, true) < 4) + return BF_SHORT; + return BF_LONG; +} + +static int battle_blewcount_bonus(map_session_data *sd, uint16 skill_id) +{ + if (sd->skillblown.empty()) + return 0; + //Apply the bonus blewcount. [Skotlex] + for (const auto &it : sd->skillblown) { + if (it.id == skill_id) + return it.val; + } + return 0; +} + +static enum e_skill_damage_type battle_skill_damage_type( struct block_list* bl ){ + switch( bl->type ){ + case BL_PC: + return SKILLDMG_PC; + case BL_MOB: + if( status_get_class_(bl) == CLASS_BOSS ){ + return SKILLDMG_BOSS; + }else{ + return SKILLDMG_MOB; + } + default: + return SKILLDMG_OTHER; + } +} + +/** + * Gets skill damage rate from a skill (based on skill_damage_db.txt) + * @param src + * @param target + * @param skill_id + * @return Skill damage rate + */ +static int battle_skill_damage_skill(struct block_list *src, struct block_list *target, uint16 skill_id) { + std::shared_ptr skill = skill_db.find(skill_id); + + if (!skill || !skill->damage.map) + return 0; + + s_skill_damage *damage = &skill->damage; + + //check the adjustment works for specified type + if (!(damage->caster&src->type)) + return 0; + + map_data *mapdata = map_getmapdata(src->m); + + if ((damage->map&1 && (!mapdata->getMapFlag(MF_PVP) && !mapdata_flag_gvg2(mapdata) && !mapdata->getMapFlag(MF_BATTLEGROUND) && !mapdata->getMapFlag(MF_SKILL_DAMAGE) && !mapdata->getMapFlag(MF_RESTRICTED))) || + (damage->map&2 && mapdata->getMapFlag(MF_PVP)) || + (damage->map&4 && mapdata_flag_gvg2(mapdata)) || + (damage->map&8 && mapdata->getMapFlag(MF_BATTLEGROUND)) || + (damage->map&16 && mapdata->getMapFlag(MF_SKILL_DAMAGE)) || + (damage->map&mapdata->zone && mapdata->getMapFlag(MF_RESTRICTED))) + { + return damage->rate[battle_skill_damage_type(target)]; + } + + return 0; +} + +/** + * Gets skill damage rate from a skill (based on 'skill_damage' mapflag) + * @param src + * @param target + * @param skill_id + * @return Skill damage rate + */ +static int battle_skill_damage_map(struct block_list *src, struct block_list *target, uint16 skill_id) { + map_data *mapdata = map_getmapdata(src->m); + + if (!mapdata || !mapdata->getMapFlag(MF_SKILL_DAMAGE)) + return 0; + + int rate = 0; + + // Damage rate for all skills at this map + if (mapdata->damage_adjust.caster&src->type) + rate = mapdata->damage_adjust.rate[battle_skill_damage_type(target)]; + + if (mapdata->skill_damage.empty()) + return rate; + + // Damage rate for specified skill at this map + if (mapdata->skill_damage.find(skill_id) != mapdata->skill_damage.end() && mapdata->skill_damage[skill_id].caster&src->type) { + rate += mapdata->skill_damage[skill_id].rate[battle_skill_damage_type(target)]; + } + return rate; +} + +/** + * Check skill damage adjustment based on mapflags and skill_damage_db.txt for specified skill + * @param src + * @param target + * @param skill_id + * @return Total damage rate + */ +static int battle_skill_damage(struct block_list *src, struct block_list *target, uint16 skill_id) { + nullpo_ret(src); + if (!target || !skill_id) + return 0; + skill_id = skill_dummy2skill_id(skill_id); + return battle_skill_damage_skill(src, target, skill_id) + battle_skill_damage_map(src, target, skill_id); +} + +/** + * Calculates Minstrel/Wanderer bonus for Chorus skills. + * @param sd: Player who has Chorus skill active + * @return Bonus value based on party count + */ +int battle_calc_chorusbonus(map_session_data *sd) { +#ifdef RENEWAL // No bonus in renewal + return 0; +#endif + + int members = 0; + + if (!sd || !sd->status.party_id) + return 0; + + members = party_foreachsamemap(party_sub_count_class, sd, 0, MAPID_THIRDMASK, MAPID_MINSTRELWANDERER); + + if (members < 3) + return 0; // Bonus remains 0 unless 3 or more Minstrels/Wanderers are in the party. + if (members > 7) + return 5; // Maximum effect possible from 7 or more Minstrels/Wanderers. + return members - 2; // Effect bonus from additional Minstrels/Wanderers if not above the max possible. +} + +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); + +/*======================================================= + * Should infinite defense be applied on target? (plant) + *------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + * flag - see e_battle_flag + */ +bool is_infinite_defense(struct block_list *target, int flag) +{ + status_data* tstatus = status_get_status_data(*target); + + if(target->type == BL_SKILL) { + TBL_SKILL *su = ((TBL_SKILL*)target); + + if (su && su->group && (su->group->skill_id == NPC_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD)) + return true; + } + + if(status_has_mode(tstatus,MD_IGNOREMELEE) && (flag&(BF_WEAPON|BF_SHORT)) == (BF_WEAPON|BF_SHORT) ) + return true; + if(status_has_mode(tstatus,MD_IGNOREMAGIC) && flag&(BF_MAGIC) ) + return true; + if(status_has_mode(tstatus,MD_IGNORERANGED) && (flag&(BF_WEAPON|BF_LONG)) == (BF_WEAPON|BF_LONG) ) + return true; + if(status_has_mode(tstatus,MD_IGNOREMISC) && flag&(BF_MISC) ) + return true; + + status_change* tsc = status_get_sc(target); + if (tsc && tsc->getSCE(SC_INVINCIBLE)) + return true; + + return false; +} + +/*======================== + * Is attack arrow based? + *------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool is_skill_using_arrow(struct block_list *src, int skill_id) +{ + if(src != nullptr) { + status_data* sstatus = status_get_status_data(*src); + map_session_data *sd = BL_CAST(BL_PC, src); + + return ((sd && sd->state.arrow_atk) || (!sd && ((skill_id && skill_get_ammotype(skill_id)) || sstatus->rhw.range>3)) + || skill_id == HT_FREEZINGTRAP || (skill_id == HT_PHANTASMIC) || (skill_id == GS_GROUNDDRIFT)); + } else + return false; +} + +/*========================================= + * Is attack right handed? By default yes. + *----------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool is_attack_right_handed(struct block_list *src, int skill_id) +{ + if(src != nullptr) { + map_session_data *sd = BL_CAST(BL_PC, src); + + //Skills ALWAYS use ONLY your right-hand weapon (tested on Aegis 10.2) + if(!skill_id && sd && sd->weapontype1 == W_FIST && sd->weapontype2 != W_FIST) + return false; + } + return true; +} + +/*======================================= + * Is attack left handed? By default no. + *--------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool is_attack_left_handed(struct block_list *src, int skill_id) +{ + if(src != nullptr) { + //Skills ALWAYS use ONLY your right-hand weapon (tested on Aegis 10.2) + if(!skill_id) { + map_session_data *sd = BL_CAST(BL_PC, src); + + if (sd) { + if (sd->weapontype1 == W_FIST && sd->weapontype2 != W_FIST) + return true; + if (sd->status.weapon == W_KATAR) + return true; + } + + struct status_data *sstatus = status_get_status_data(*src); + + if (sstatus->lhw.atk) + return true; + } + } + return false; +} + +/*============================= + * Do we score a critical hit? + *----------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool is_attack_critical(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, bool first_call) +{ + if (!first_call) + return (wd->type == DMG_CRITICAL || wd->type == DMG_MULTI_HIT_CRITICAL); + + if (skill_id == NPC_CRITICALSLASH || skill_id == LG_PINPOINTATTACK) //Always critical skills + return true; + + if( skill_id && !skill_get_nk(skill_id,NK_CRITICAL) ) + return false; + + status_data* sstatus = status_get_status_data(*src); + + if( sstatus->cri ) + { + map_session_data *sd = BL_CAST(BL_PC, src); + + if(wd->type == DMG_MULTI_HIT){ //Multiple Hit Attack Skills. + if(pc_checkskill(sd,GS_CHAINACTION) && !skill_get_nk(GS_CHAINACTION,NK_CRITICAL)) //Chain Action + return false; + + if(pc_checkskill(sd,TF_DOUBLE) && !skill_get_nk(TF_DOUBLE,NK_CRITICAL)) //Double Attack + return false; + } + + status_data* tstatus = status_get_status_data(*target); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + map_session_data *tsd = BL_CAST(BL_PC, target); + short cri = sstatus->cri; + + if (sd) { + cri += sd->indexed_bonus.critaddrace[tstatus->race] + sd->indexed_bonus.critaddrace[RC_ALL]; + if(!skill_id && is_skill_using_arrow(src, skill_id)) { + cri += sd->bonus.arrow_cri; + cri += sd->bonus.critical_rangeatk; + } + } + + if(sc && sc->getSCE(SC_CAMOUFLAGE)) + cri += 100 * min(10,sc->getSCE(SC_CAMOUFLAGE)->val3); //max 100% (1K) + + //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->getSCE(SC_SLEEP) ) + cri *= 2; + + switch(skill_id) { + case 0: + if(sc && !sc->getSCE(SC_AUTOCOUNTER)) + break; + clif_specialeffect(src, EF_AUTOCOUNTER, AREA); + status_change_end(src, SC_AUTOCOUNTER); + [[fallthrough]]; + case KN_AUTOCOUNTER: + if(battle_config.auto_counter_type && + (battle_config.auto_counter_type&src->type)) + return true; + else + cri *= 2; + break; + case SN_SHARPSHOOTING: + case MA_SHARPSHOOTING: +#ifdef RENEWAL + cri += 300; // !TODO: Confirm new bonus +#else + cri += 200; +#endif + break; + case NJ_KIRIKAGE: + cri += 250 + 50*skill_lv; + break; +#ifdef RENEWAL + case ASC_BREAKER: +#endif + case GC_CROSSIMPACT: + case SHC_SAVAGE_IMPACT: + case SHC_ETERNAL_SLASH: + case SHC_IMPACT_CRATER: + cri /= 2; + break; + case WH_GALESTORM: + if (sc && !sc->getSCE(SC_CALAMITYGALE)) + return false; + break; + case NW_ONLY_ONE_BULLET: + case NW_SPIRAL_SHOOTING: + if( sd == nullptr || sd->weapontype1 != W_RIFLE ){ + return false; + } + break; + case NW_MAGAZINE_FOR_ONE: + if( sd == nullptr || sd->weapontype1 != W_REVOLVER ){ + return false; + } + break; + } + if(tsd && tsd->bonus.critical_def) + cri = cri * ( 100 - tsd->bonus.critical_def ) / 100; + return (rnd()%1000 < cri); + } + return false; +} + +/*========================================================== + * Is the attack piercing? (Investigate/Ice Pick in pre-re) + *---------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static int is_attack_piercing(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, short weapon_position) +{ + if (skill_id == MO_INVESTIGATE || skill_id == RL_MASS_SPIRAL) + return 2; + + if(src != nullptr) { + map_session_data *sd = BL_CAST(BL_PC, src); + status_data* tstatus = status_get_status_data(*target); + + if( skill_id != PA_SACRIFICE && skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS && skill_id != PA_SHIELDCHAIN && skill_id != KO_HAPPOKUNAI +#ifndef RENEWAL + && !is_attack_critical(wd, src, target, skill_id, skill_lv, false) +#endif + ) + { //Elemental/Racial adjustments + if( sd && (sd->right_weapon.def_ratio_atk_ele & (1<def_ele) || sd->right_weapon.def_ratio_atk_ele & (1<right_weapon.def_ratio_atk_race & (1<race) || sd->right_weapon.def_ratio_atk_race & (1<right_weapon.def_ratio_atk_class & (1<class_) || sd->right_weapon.def_ratio_atk_class & (1<left_weapon.def_ratio_atk_ele & (1<def_ele) || sd->left_weapon.def_ratio_atk_ele & (1<left_weapon.def_ratio_atk_race & (1<race) || sd->left_weapon.def_ratio_atk_race & (1<left_weapon.def_ratio_atk_class & (1<class_) || sd->left_weapon.def_ratio_atk_class & (1< battle_skill_get_damage_properties(uint16 skill_id, int is_splash) +{ + if (skill_id == 0) { + if (is_splash) { + std::bitset tmp_nk; + + tmp_nk.set(NK_IGNOREATKCARD); + tmp_nk.set(NK_IGNOREFLEE); + + return tmp_nk; + } else + return 0; + } else + return skill_db.find(skill_id)->nk; +} + +/*============================= + * Checks if attack is hitting + *----------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool is_attack_hitting(struct Damage* wd, struct block_list *src, struct block_list *target, int skill_id, int skill_lv, bool first_call) +{ + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + map_session_data *sd = BL_CAST(BL_PC, src); + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd->miscflag); + short flee, hitrate; + + if (!first_call) + return (wd->dmg_lv != ATK_FLEE); + if (is_attack_critical(wd, src, target, skill_id, skill_lv, false)) + return true; + else if(sd && sd->bonus.perfect_hit > 0 && rnd()%100 < sd->bonus.perfect_hit) + return true; + else if (sc && sc->getSCE(SC_FUSION)) + return true; + else if ((skill_id == AS_SPLASHER || skill_id == GN_SPORE_EXPLOSION) && !wd->miscflag) + return true; + else if (skill_id == CR_SHIELDBOOMERANG && sc && sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_CRUSADER ) + return true; + else if (tsc && tsc->opt1 && tsc->opt1 != OPT1_STONEWAIT && tsc->opt1 != OPT1_BURNING) + return true; + else if (nk[NK_IGNOREFLEE]) + return true; + + if( tsc && tsc->getSCE(SC_NEUTRALBARRIER) && (wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG ) + return false; + + 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 = unit_counttargeted(target); //256 max targets should be a sane max + + 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 //assume 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; + + //Fogwall's hit penalty is only for normal ranged attacks. + if ((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG && !skill_id && tsc && tsc->getSCE(SC_FOGWALL)) + hitrate -= 50; + + if(sd && is_skill_using_arrow(src, skill_id)) + 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 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_CHANGEUNDEAD: + case NPC_EARTHQUAKE: + case NPC_POISON: + case NPC_BLINDATTACK: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + case NPC_PETRIFYATTACK: + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case NPC_BLEEDING: + case NPC_BLEEDING2: + hitrate += hitrate * 20 / 100; + break; + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_ICEBREATH2: + case NPC_THUNDERBREATH: + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + hitrate *= 2; + 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) +#ifdef RENEWAL + hitrate += hitrate * 90 / 100; +#else + hitrate += hitrate * 50 / 100; +#endif + break; +#ifdef RENEWAL + case RG_BACKSTAP: + hitrate += skill_lv; // !TODO: What's the rate increase? + break; +#endif + case RK_SONICWAVE: + hitrate += hitrate * 3 * skill_lv / 100; // !TODO: Confirm the hitrate bonus + 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 LG_BANISHINGPOINT: + hitrate += 5 * skill_lv; + break; + case GC_VENOMPRESSURE: + hitrate += 10 + 4 * skill_lv; + break; + case SC_FATALMENACE: + if (skill_lv < 6) + hitrate -= 35 - 5 * skill_lv; + else if (skill_lv > 6) + hitrate += 5 * skill_lv - 30; + break; + case RL_SLUGSHOT: + { + int8 dist = distance_bl(src, target); + if (dist > 3) { + // Reduce n hitrate for each cell after initial 3 cells. Different each level + // -10:-9:-8:-7:-6 + dist -= 3; + hitrate -= ((11 - skill_lv) * dist); + } + } + break; + } + } else if (sd && wd->type&DMG_MULTI_HIT && wd->div_ == 2) // +1 hit per level of Double Attack on a successful double attack (making sure other multi attack skills do not trigger this) [helvetica] + hitrate += pc_checkskill(sd,TF_DOUBLE); + + if (sd) { + int skill = 0; + + // 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; + } + + if (sc) { + if (sc->getSCE(SC_MTF_ASPD)) + hitrate += sc->getSCE(SC_MTF_ASPD)->val2; + if (sc->getSCE(SC_MTF_ASPD2)) + hitrate += sc->getSCE(SC_MTF_ASPD2)->val2; + } + + hitrate = cap_value(hitrate, battle_config.min_hitrate, battle_config.max_hitrate); + + if(skill_id == PA_SHIELDCHAIN) + hitrate += 20; //Rapid Smiting gives a flat +20 hit after the hitrate was capped + + return (rnd()%100 < hitrate); +} + +/*========================================== + * If attack ignores def. + *------------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static bool attack_ignores_def(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, short weapon_position) +{ + status_data* tstatus = status_get_status_data(*target); + status_change *sc = status_get_sc(src); + map_session_data *sd = BL_CAST(BL_PC, src); + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd->miscflag); + +#ifndef RENEWAL + if (is_attack_critical(wd, src, target, skill_id, skill_lv, false)) + return true; + else +#endif + if (sc && sc->getSCE(SC_FUSION)) + return true; + + if( sd != nullptr ){ + switch( skill_id ){ + case RK_WINDCUTTER: + if( sd->status.weapon == W_2HSWORD ){ + return true; + } + break; + case NW_THE_VIGILANTE_AT_NIGHT: + if( sd->status.weapon == W_GATLING ){ + return true; + } + break; + case NW_ONLY_ONE_BULLET: + if( sd->status.weapon == W_REVOLVER ){ + return true; + } + break; + } + } + + if (skill_id != CR_GRANDCROSS && skill_id != NPC_GRANDDARKNESS) + { //Ignore Defense? + if (sd && (sd->right_weapon.ignore_def_ele & (1<def_ele) || sd->right_weapon.ignore_def_ele & (1<right_weapon.ignore_def_race & (1<race) || sd->right_weapon.ignore_def_race & (1<right_weapon.ignore_def_class & (1<class_) || sd->right_weapon.ignore_def_class & (1<left_weapon.ignore_def_ele & (1<def_ele) || sd->left_weapon.ignore_def_ele & (1<left_weapon.ignore_def_race & (1<race) || sd->left_weapon.ignore_def_race & (1<left_weapon.ignore_def_class & (1<class_) || sd->left_weapon.ignore_def_class & (1<equip_index[EQI_AMMO]; + // Attack of cannon balls is not added to equip attack, it needs to be added by the skills that use them + if (ammo_idx >= 0 && sd->inventory_data[ammo_idx] != nullptr && sd->inventory_data[ammo_idx]->subtype != AMMO_CANNONBALL) + eatk += sd->bonus.arrow_atk; + } + + return eatk + status->eatk; + } + return 0; // shouldn't happen but just in case +} +#endif + +/*======================================== + * Returns the element type of attack + *---------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +int battle_get_weapon_element(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, short weapon_position, bool calc_for_damage_only) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + int element = skill_get_ele(skill_id, skill_lv); + + //Take weapon's element + if( !skill_id || element == ELE_WEAPON ) { + if (weapon_position == EQI_HAND_R) + element = sstatus->rhw.ele; + else + element = sstatus->lhw.ele; + if(is_skill_using_arrow(src, skill_id) && sd && sd->bonus.arrow_ele && weapon_position == EQI_HAND_R) + element = sd->bonus.arrow_ele; + if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm >= MAX_SPIRITCHARM) + element = sd->spiritcharm_type; // Summoning 10 spiritcharm will endow your weapon + // on official endows override all other elements [helvetica] + if(sc && sc->getSCE(SC_ENCHANTARMS)) // Check for endows + element = sc->getSCE(SC_ENCHANTARMS)->val1; + } else if( element == ELE_ENDOWED ) //Use enchantment's element + element = status_get_attack_sc_element(src,sc); + else if( element == ELE_RANDOM ) //Use random element + element = rnd()%ELE_ALL; + + switch( skill_id ) { + case GS_GROUNDDRIFT: + element = wd->miscflag; //element comes in flag. + break; + case LK_SPIRALPIERCE: + if (!sd) + element = ELE_NEUTRAL; //forced neutral for monsters + break; + case RK_DRAGONBREATH: + if (sc) { + if (sc->getSCE(SC_LUXANIMA)) // Lux Anima has priority over Giant Growth + element = ELE_DARK; + else if (sc->getSCE(SC_GIANTGROWTH)) + element = ELE_HOLY; + } + break; + case RK_DRAGONBREATH_WATER: + if (sc) { + if (sc->getSCE(SC_LUXANIMA)) // Lux Anima has priority over Fighting Spirit + element = ELE_NEUTRAL; + else if (sc->getSCE(SC_FIGHTINGSPIRIT)) + element = ELE_GHOST; + } + break; + case LG_HESPERUSLIT: + if (sc && sc->getSCE(SC_BANDING) && sc->getSCE(SC_BANDING)->val2 > 4) + element = ELE_HOLY; + break; + case GN_CARTCANNON: + case NC_ARMSCANNON: + if (sd && sd->state.arrow_atk > 0) + element = sd->bonus.arrow_ele; + break; + case SJ_PROMINENCEKICK: + element = ELE_FIRE; + break; + case RL_H_MINE: + if (sd && sd->flicker) //Force RL_H_MINE deals fire damage if activated by RL_FLICKER + element = ELE_FIRE; + break; + case NW_BASIC_GRENADE: + case NW_HASTY_FIRE_IN_THE_HOLE: + case NW_GRENADES_DROPPING: + case NW_MISSION_BOMBARD: + // Night Watch Grenade Fragment elementals affecting those skills. + if( sc != nullptr ){ + if( sc->getSCE( SC_GRENADE_FRAGMENT_1 ) != nullptr ){ + element = ELE_WATER; + }else if( sc->getSCE( SC_GRENADE_FRAGMENT_2 ) != nullptr ){ + element = ELE_WIND; + }else if( sc->getSCE( SC_GRENADE_FRAGMENT_3 ) != nullptr ){ + element = ELE_EARTH; + }else if( sc->getSCE( SC_GRENADE_FRAGMENT_4 ) != nullptr ){ + element = ELE_FIRE; + }else if( sc->getSCE( SC_GRENADE_FRAGMENT_5 ) != nullptr ){ + element = ELE_DARK; + }else if( sc->getSCE( SC_GRENADE_FRAGMENT_6 ) != nullptr ){ + element = ELE_HOLY; + } + } + break; + } + + if (sc && sc->getSCE(SC_GOLDENE_FERSE) && ((!skill_id && (rnd() % 100 < sc->getSCE(SC_GOLDENE_FERSE)->val4)) || skill_id == MH_STAHL_HORN)) + element = ELE_HOLY; + +// calc_flag means the element should be calculated for damage only + if (calc_for_damage_only) + return element; + +#ifdef RENEWAL + if (skill_id == CR_SHIELDBOOMERANG) + element = ELE_NEUTRAL; +#endif + + return element; +} + +int battle_get_magic_element(struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv, int mflag) { + int element = skill_get_ele(skill_id, skill_lv); + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + + if (element == ELE_WEAPON) { // pl=-1 : the skill takes the weapon's element + element = sstatus->rhw.ele; + if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm >= MAX_SPIRITCHARM) + element = sd->spiritcharm_type; // Summoning 10 spiritcharm will endow your weapon + } else if (element == ELE_ENDOWED) //Use status element + element = status_get_attack_sc_element(src,status_get_sc(src)); + else if (element == ELE_RANDOM) //Use random element + element = rnd()%ELE_ALL; + + switch(skill_id) { + case NPC_EARTHQUAKE: + element = ELE_NEUTRAL; + break; + case WL_HELLINFERNO: + if (mflag & 2) { // ELE_DARK + element = ELE_DARK; + } + break; + case NPC_PSYCHIC_WAVE: + case SO_PSYCHIC_WAVE: + if( sc && sc->count ) { + static const std::vector types = { + SC_HEATER_OPTION, + SC_COOLER_OPTION, + SC_BLAST_OPTION, + SC_CURSED_SOIL_OPTION, + SC_FLAMETECHNIC_OPTION, + SC_COLD_FORCE_OPTION, + SC_GRACE_BREEZE_OPTION, + SC_EARTH_CARE_OPTION, + SC_DEEP_POISONING_OPTION + }; + for( sc_type type : types ){ + if( sc->getSCE( type ) ){ + element = sc->getSCE( type )->val3; + break; + } + } + } + break; + case KO_KAIHOU: + if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0) + element = sd->spiritcharm_type; + break; + case AB_ADORAMUS: + if (sc && sc->getSCE(SC_ANCILLA)) + element = ELE_NEUTRAL; + break; + case LG_RAYOFGENESIS: + if (sc && sc->getSCE(SC_INSPIRATION)) + element = ELE_NEUTRAL; + break; + case WM_REVERBERATION: + case TR_METALIC_FURY: + case TR_SOUNDBLEND: + if (sd) + element = sd->bonus.arrow_ele; + break; + } + + return element; +} + +int battle_get_misc_element(struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv, int mflag) { + int element = skill_get_ele(skill_id, skill_lv); + + if (element == ELE_WEAPON || element == ELE_ENDOWED) //Attack that takes weapon's element for misc attacks? Make it neutral [Skotlex] + element = ELE_NEUTRAL; + else if (element == ELE_RANDOM) //Use random element + element = rnd()%ELE_ALL; + + return element; +} + +#define ATK_RATE(damage, damage2, a) do { int64 rate_ = (a); (damage) = (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) = (damage2) * rate_ / 100; } while(0); +#define ATK_RATE2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) = (damage) *rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) = (damage2) * rate2_ / 100; } while(0); +#define ATK_RATER(damage, a) { (damage) = (damage) * (a) / 100; } +#define ATK_RATEL(damage2, a) { (damage2) = (damage2) * (a) / 100; } +//Adds dmg%. 100 = +100% (double) damage. 10 = +10% damage +#define ATK_ADDRATE(damage, damage2, a) do { int64 rate_ = (a); (damage) += (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) += (damage2) *rate_ / 100; } while(0); +#define ATK_ADDRATE2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) += (damage) * rate_ / 100; if(is_attack_left_handed(src, skill_id)) (damage2) += (damage2) * rate2_ / 100; } while(0); +//Adds an absolute value to damage. 100 = +100 damage +#define ATK_ADD(damage, damage2, a) do { int64 rate_ = (a); (damage) += rate_; if(is_attack_left_handed(src, skill_id)) (damage2) += rate_; } while(0); +#define ATK_ADD2(damage, damage2, a , b) do { int64 rate_ = (a), rate2_ = (b); (damage) += rate_; if(is_attack_left_handed(src, skill_id)) (damage2) += rate2_; } while(0); + +#ifdef RENEWAL + #define RE_ALLATK_ADD(wd, a) do { int64 a_ = (a); ATK_ADD((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_ADD((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_ADD((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_ADD((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0); + #define RE_ALLATK_RATE(wd, a) do { int64 a_ = (a); ATK_RATE((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_RATE((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_RATE((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_RATE((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0); + #define RE_ALLATK_ADDRATE(wd, a) do { int64 a_ = (a); ATK_ADDRATE((wd)->statusAtk, (wd)->statusAtk2, a_); ATK_ADDRATE((wd)->weaponAtk, (wd)->weaponAtk2, a_); ATK_ADDRATE((wd)->equipAtk, (wd)->equipAtk2, a_); ATK_ADDRATE((wd)->masteryAtk, (wd)->masteryAtk2, a_); } while(0); +#else + #define RE_ALLATK_ADD(wd, a) {;} + #define RE_ALLATK_RATE(wd, a) {;} + #define RE_ALLATK_ADDRATE(wd, a) {;} +#endif + +/** + * Cap both damage and basedamage of damage struct to a minimum value + * @param wd: Weapon damage structure + * @param src: Source of the attack + * @param skill_id: Skill ID of the skill used by source + * @param min: Minimum value to which damage should be capped + */ +static void battle_min_damage(struct Damage &wd, struct block_list &src, uint16 skill_id, int64 min) { + if (is_attack_right_handed(&src, skill_id)) { + wd.damage = cap_value(wd.damage, min, INT64_MAX); +#ifndef RENEWAL + wd.basedamage = cap_value(wd.basedamage, min, INT64_MAX); +#endif + } + // Left-hand damage is always capped to 0 + if (is_attack_left_handed(&src, skill_id)) { + wd.damage2 = cap_value(wd.damage2, 0, INT64_MAX); + } +} + +/** + * Returns the bonus damage granted by Spirit Spheres + * As we delete the spheres before calculating the damage, we need some kind of logic to figure out how many were available + * Each skill handles this in its own way, this function handles the different cases + * @param wd: Weapon damage structure + * @param src: Source of the attack + * @param skill_id: Skill ID of the skill used by source + * @param min: Minimum value to which damage should be capped + */ +static int battle_get_spiritball_damage(struct Damage& wd, struct block_list& src, uint16 skill_id) { + + map_session_data* sd = BL_CAST(BL_PC, &src); + + // Return 0 for non-players + if (!sd) + return 0; + + int damage = 0; + + switch (skill_id) { + case MO_INVESTIGATE: +#ifndef RENEWAL + case MO_FINGEROFFENSIVE: +#endif + // These skills used as many spheres as they do hits + damage = (wd.div_ + sd->spiritball) * 3; + break; + case MO_EXTREMITYFIST: + // These skills store the number of spheres the player had before cast + damage = sd->spiritball_old * 3; + break; + default: + // Any skills that do not consume spheres or do not care + damage = sd->spiritball * 3; + break; + } + + return damage; +} + +/*======================================== + * Do element damage modifier calculation + *---------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_element_damage(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv) +{ + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd->miscflag); + map_session_data* sd = BL_CAST(BL_PC, src); + status_change* sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, true); + + // Elemental attribute fix + if(!nk[NK_IGNOREELEMENT] && (wd->damage > 0 || wd->damage2 > 0)) { + int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, true); + + switch (skill_id) { + case PA_SACRIFICE: + case RK_DRAGONBREATH: + case RK_DRAGONBREATH_WATER: + case NC_SELFDESTRUCTION: + case HFLI_SBR44: + wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv, 1); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv, 1); + break; + default: + if (skill_id == 0 && (battle_config.attack_attr_none & src->type)) + return; // Non-player basic attacks are non-elemental, they deal 100% against all defense elements +#ifdef RENEWAL + if (sd == nullptr) { // Renewal player's elemental damage calculation is already done before this point, only calculate for everything else +#endif + wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv, 1); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv, 1); +#ifdef RENEWAL + } +#endif + break; + } + + // Forced to neutral skills [helvetica] + // Skills forced to neutral gain benefits from weapon element but final damage is considered "neutral" and resistances are applied again + switch (skill_id) { +#ifdef RENEWAL + case MO_INVESTIGATE: + case CR_SHIELDBOOMERANG: + case PA_SHIELDCHAIN: +#endif + case MC_CARTREVOLUTION: + case HW_MAGICCRASHER: + case SR_FALLENEMPIRE: + case SR_CRESCENTELBOW_AUTOSPELL: + case SR_GATEOFHELL: + case GN_FIRE_EXPANSION_ACID: + wd->damage = battle_attr_fix(src, target, wd->damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv, 1); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 = battle_attr_fix(src, target, wd->damage2, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv, 1); + break; + } + } + +#ifndef RENEWAL + // These mastery bonuses are non-elemental and should apply even if the attack misses + // They are still increased by the EDP/Magnum Break bonus damage (WATK_ELEMENT) + // In renewal these bonuses do not apply when the attack misses + if (sd && battle_skill_stacks_masteries_vvs(skill_id, BCHK_STAR)) { + // Star Crumb bonus damage + ATK_ADD2(wd->damage, wd->damage2, sd->right_weapon.star, sd->left_weapon.star); + } + // Check if general mastery bonuses apply (above check is only for Star Crumb) + if (battle_skill_stacks_masteries_vvs(skill_id, BCHK_ALL)) { + // Spirit Sphere bonus damage + ATK_ADD(wd->damage, wd->damage2, battle_get_spiritball_damage(*wd, *src, skill_id)); + + // Skill-specific bonuses + switch(skill_id) { + case TF_POISON: + ATK_ADD(wd->damage, wd->damage2, 15 * skill_lv); + // Envenom applies the attribute table to the base damage and then again to the final damage + wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv, 1); + break; + case NJ_SYURIKEN: + ATK_ADD(wd->damage, wd->damage2, 4 * skill_lv); + if (sd) { + ATK_ADD(wd->damage, wd->damage2, 3 * pc_checkskill(sd, NJ_TOBIDOUGU)); + ATK_ADD(wd->damage, wd->damage2, sd->bonus.arrow_atk); + } + // Applies attribute table on neutral element to the final damage + wd->damage = battle_attr_fix(src, target, wd->damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv, 1); + break; + case NJ_KUNAI: + if (sd) { + ATK_ADD(wd->damage, wd->damage2, 3 * sd->bonus.arrow_atk); + } + // Applies attribute table on neutral element to the final damage + wd->damage = battle_attr_fix(src, target, wd->damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv, 1); + break; + } + } + + // These bonuses do not apply to skills that ignore +% damage cards + // If damage was reduced below 0 and was not increased again to a positive value through mastery bonuses, these bonuses are ignored + // Any of these are only applied to your right hand weapon in pre-renewal + if (!nk[NK_IGNOREELEMENT] && !nk[NK_IGNOREATKCARD] && (wd->damage > 0 || wd->damage2 > 0) && sc) { + + // EDP bonus damage + // This has to be applied after mastery bonuses but still before the elemental extra damage + if (sc->getSCE(SC_EDP)) + wd->damage += (wd->damage * sc->getSCE(SC_EDP)->val3) / 100; + + // This adds a percentual damage bonus based on the damage you would deal with a normal attack + // Applies only to player damage; monsters and mercenaries don't get this damage boost + if (sd && sc->getSCE(SC_WATK_ELEMENT)) { + // Pretend the normal attack was of the element stored in the status change + wd->basedamage = battle_attr_fix(src, target, wd->basedamage, sc->getSCE(SC_WATK_ELEMENT)->val1, tstatus->def_ele, tstatus->ele_lv, 1); + // Star Crumb bonus damage + wd->basedamage += sd->right_weapon.star; + // Spirit Sphere bonus damage + wd->basedamage += battle_get_spiritball_damage(*wd, *src, skill_id); + // Add percent of the base damage to the damage + wd->damage += (wd->basedamage * sc->getSCE(SC_WATK_ELEMENT)->val2) / 100; + } + } +#endif + // Cap damage to 0 + if (battle_config.attr_recover == 0) + battle_min_damage(*wd, *src, skill_id, 0); +} + +/*================================== + * Calculate weapon mastery damages + *---------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_attack_masteries(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + int t_class = status_get_class(target); + +#ifndef RENEWAL + if (sd) { + wd->basedamage = battle_addmastery(sd, target, wd->basedamage, 0); + } +#endif + + // Check if mastery damage applies to current skill + if (sd && battle_skill_stacks_masteries_vvs(skill_id, BCHK_ALL)) + { //Add mastery damage + uint16 skill; + + wd->damage = battle_addmastery(sd,target,wd->damage,0); +#ifdef RENEWAL + wd->masteryAtk = battle_addmastery(sd,target,wd->weaponAtk,0); +#endif + if (is_attack_left_handed(src, skill_id)) { + wd->damage2 = battle_addmastery(sd,target,wd->damage2,1); +#ifdef RENEWAL + wd->masteryAtk2 = battle_addmastery(sd,target,wd->weaponAtk2,1); +#endif + } + +#ifdef RENEWAL + //General skill masteries + if(skill_id == TF_POISON) //Additional ATK from Envenom is treated as mastery type damage [helvetica] + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 15 * skill_lv); + if (skill_id != MC_CARTREVOLUTION && pc_checkskill(sd, BS_HILTBINDING) > 0) + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 4); + // Star Crumb bonus damage + ATK_ADD2(wd->masteryAtk, wd->masteryAtk2, sd->right_weapon.star, sd->left_weapon.star); + // Spirit Sphere bonus damage + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, battle_get_spiritball_damage(*wd, *src, skill_id)); + + if (skill_id == NJ_SYURIKEN && (skill = pc_checkskill(sd,NJ_TOBIDOUGU)) > 0) { + ATK_ADD(wd->damage, wd->damage2, 3 * skill); + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 3 * skill); + } +#endif + + switch(skill_id) { + case RA_WUGDASH: + case RA_WUGSTRIKE: + case RA_WUGBITE: + if (sd) { + skill = pc_checkskill(sd, RA_TOOTHOFWUG); + + ATK_ADD(wd->damage, wd->damage2, 30 * skill); +#ifdef RENEWAL + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 30 * skill); +#endif + } + break; +#ifdef RENEWAL + case GN_CARTCANNON: + case NC_ARMSCANNON: + // Arrow attack of these skills is not influenced by P.ATK so we add it as mastery attack + if (sd != nullptr) { + status_data* tstatus = status_get_status_data(*target); + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, battle_attr_fix(src, target, sd->bonus.arrow_atk, sd->bonus.arrow_ele, tstatus->def_ele, tstatus->ele_lv)); + } + break; +#endif + } + + if (sc) { // Status change considered as masteries +#ifdef RENEWAL + if (sc->getSCE(SC_NIBELUNGEN)) // With renewal, the level 4 weapon limitation has been removed + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, sc->getSCE(SC_NIBELUNGEN)->val2); +#endif + + if(sc->getSCE(SC_CAMOUFLAGE)) { + ATK_ADD(wd->damage, wd->damage2, 30 * min(10, sc->getSCE(SC_CAMOUFLAGE)->val3)); +#ifdef RENEWAL + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 30 * min(10, sc->getSCE(SC_CAMOUFLAGE)->val3)); +#endif + } + if(sc->getSCE(SC_GN_CARTBOOST)) { + ATK_ADD(wd->damage, wd->damage2, 10 * sc->getSCE(SC_GN_CARTBOOST)->val1); +#ifdef RENEWAL + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, 10 * sc->getSCE(SC_GN_CARTBOOST)->val1); +#endif + } + if (sc->getSCE(SC_P_ALTER)) { + ATK_ADD(wd->damage, wd->damage2, sc->getSCE(SC_P_ALTER)->val2); +#ifdef RENEWAL + ATK_ADD(wd->masteryAtk, wd->masteryAtk2, sc->getSCE(SC_P_ALTER)->val2); +#endif + } + } + } +} + +#ifdef RENEWAL +/*========================================= + * Calculate the various Renewal ATK parts + *----------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_damage_parts(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + map_session_data *sd = BL_CAST(BL_PC, src); + bool critical = false; + + int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, false); + int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, false); + + wd->statusAtk += sstatus->batk; + wd->statusAtk2 += sstatus->batk; + + if (sd && sd->sc.getSCE(SC_SEVENWIND)) { // Mild Wind applies element to status ATK as well as weapon ATK [helvetica] + wd->statusAtk = battle_attr_fix(src, target, wd->statusAtk, right_element, tstatus->def_ele, tstatus->ele_lv); + wd->statusAtk2 = battle_attr_fix(src, target, wd->statusAtk2, left_element, tstatus->def_ele, tstatus->ele_lv); + } else { // status atk is considered neutral on normal attacks [helvetica] + wd->statusAtk = battle_attr_fix(src, target, wd->statusAtk, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv); + wd->statusAtk2 = battle_attr_fix(src, target, wd->statusAtk2, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv); + } + + // Right-hand status attack is doubled after elemental adjustments + wd->statusAtk *= 2; + + // Check critical + if (wd->type == DMG_MULTI_HIT_CRITICAL || wd->type == DMG_CRITICAL) + critical = true; + + wd->weaponAtk += battle_calc_base_weapon_attack(src, tstatus, &sstatus->rhw, sd, critical); + wd->weaponAtk2 += battle_calc_base_weapon_attack(src, tstatus, &sstatus->lhw, sd, critical); + + // Weapon ATK gain bonus from SC_SUB_WEAPONPROPERTY here ( +x% pseudo element damage) + if (sd && sd->sc.getSCE(SC_SUB_WEAPONPROPERTY)) { + int64 bonus_atk = (int64)floor((float)( wd->weaponAtk * sd->sc.getSCE(SC_SUB_WEAPONPROPERTY)->val2 / 100)); + int64 bonus_atk2 = (int64)floor((float)( wd->weaponAtk2 * sd->sc.getSCE(SC_SUB_WEAPONPROPERTY)->val2 / 100)); + bonus_atk = battle_attr_fix(src, target, bonus_atk, sd->sc.getSCE(SC_SUB_WEAPONPROPERTY)->val1, tstatus->def_ele, tstatus->ele_lv); + bonus_atk2 = battle_attr_fix(src, target, bonus_atk2, sd->sc.getSCE(SC_SUB_WEAPONPROPERTY)->val1, tstatus->def_ele, tstatus->ele_lv); + + wd->weaponAtk += bonus_atk; + wd->weaponAtk2 += bonus_atk2; + } + + wd->weaponAtk = battle_attr_fix(src, target, wd->weaponAtk, right_element, tstatus->def_ele, tstatus->ele_lv); + wd->weaponAtk2 = battle_attr_fix(src, target, wd->weaponAtk2, left_element, tstatus->def_ele, tstatus->ele_lv); + + wd->equipAtk += battle_calc_equip_attack(src, skill_id); + wd->equipAtk = battle_attr_fix(src, target, wd->equipAtk, right_element, tstatus->def_ele, tstatus->ele_lv); + + wd->equipAtk2 += battle_calc_equip_attack(src, skill_id); + wd->equipAtk2 = battle_attr_fix(src, target, wd->equipAtk2, left_element, tstatus->def_ele, tstatus->ele_lv); + + // AtkRate gives a static bonus from (W.ATK + E.ATK) + if (sd && sd->bonus.atk_rate) { + wd->percentAtk = (wd->weaponAtk + wd->equipAtk) * sd->bonus.atk_rate / 100; + wd->percentAtk2 = (wd->weaponAtk2 + wd->equipAtk2) * sd->bonus.atk_rate / 100; + } + + //Mastery ATK is a special kind of ATK that has no elemental properties + //Because masteries are not elemental, they are unaffected by Ghost armors or Raydric Card + battle_calc_attack_masteries(wd, src, target, skill_id, skill_lv); + + wd->damage = 0; + wd->damage2 = 0; +} +#endif + +/*========================================================== + * Calculate basic ATK that goes into the skill ATK formula + *---------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_skill_base_damage(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + map_session_data *sd = BL_CAST(BL_PC, src); + map_session_data *tsd = BL_CAST(BL_PC, target); + + uint16 bflag = BDMG_NONE; +#ifndef RENEWAL + uint16 i; +#endif + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd->miscflag); + + switch (skill_id) { //Calc base damage according to skill + case PA_SACRIFICE: + wd->damage = sstatus->max_hp* 9/100; + wd->damage2 = 0; +#ifdef RENEWAL + wd->weaponAtk = wd->damage; + wd->weaponAtk2 = wd->damage2; +#endif + break; +#ifdef RENEWAL + case LK_SPIRALPIERCE: + case ML_SPIRALPIERCE: + if (sd) { + battle_calc_damage_parts(wd, src, target, skill_id, skill_lv); + + // Officially statusAtk + weaponAtk + equipAtk make base attack + // We simulate this here by adding them all into equip attack + ATK_ADD2(wd->equipAtk, wd->equipAtk2, wd->statusAtk + wd->weaponAtk, wd->statusAtk2 + wd->weaponAtk2); + // Set statusAtk and weaponAtk to 0 + ATK_RATE(wd->statusAtk, wd->statusAtk2, 0); + ATK_RATE(wd->weaponAtk, wd->weaponAtk2, 0); + + // Add weight + short index = sd->equip_index[EQI_HAND_R]; + if (index >= 0 && + sd->inventory_data[index] && + sd->inventory_data[index]->type == IT_WEAPON) + wd->equipAtk += sd->inventory_data[index]->weight / 10; + + // 70% damage modifier is applied to base attack + weight + ATK_RATE(wd->equipAtk, wd->equipAtk2, 70); + + // Additional skill-specific size fix + switch (tstatus->size) { + case SZ_SMALL: //Small: 130% + ATK_RATE(wd->equipAtk, wd->equipAtk2, 130); + break; + case SZ_MEDIUM: //Medium: 115% + ATK_RATE(wd->equipAtk, wd->equipAtk2, 115); + break; + //case SZ_BIG: //Large: 100% + } + } else { + wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0); //Monsters have no weight and use ATK instead + } +#else + case NJ_ISSEN: + wd->damage = 40 * sstatus->str + sstatus->hp * 8 * skill_lv / 100; + 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 + + ATK_ADDRATE(wd->damage, wd->damage2, 50*skill_lv); //Skill modifier applies to weight only. + } else { + wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0); //Monsters have no weight and use ATK instead + } + + i = sstatus->str/10; + i*=i; + ATK_ADD(wd->damage, wd->damage2, i); //Add str bonus. + switch (tstatus->size) { //Size-fix. Is this modified by weapon perfection? + case SZ_SMALL: //Small: 125% + ATK_RATE(wd->damage, wd->damage2, 125); + break; + //case SZ_MEDIUM: //Medium: 100% + case SZ_BIG: //Large: 75% + ATK_RATE(wd->damage, wd->damage2, 75); + break; + } +#endif + break; + case CR_SHIELDBOOMERANG: + case PA_SHIELDCHAIN: + wd->damage = sstatus->batk; + if (sd) { + short index = sd->equip_index[EQI_HAND_L]; + + //Base damage of shield skills is [batk + 4*refine + weight] + if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) { + ATK_ADD(wd->damage, wd->damage2, 4 * sd->inventory.u.items_inventory[index].refine); + ATK_ADD(wd->damage, wd->damage2, sd->inventory_data[index]->weight / 10); +#ifdef RENEWAL + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, 4 * sd->inventory.u.items_inventory[index].refine); + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, sd->inventory_data[index]->weight / 10); +#endif + } +#ifndef RENEWAL + // Shield Boomerang and Rapid Smiting calculate DEF before the skill ratio + battle_calc_defense_reduction(wd, src, target, skill_id, skill_lv); +#endif + } else + ATK_ADD(wd->damage, wd->damage2, sstatus->rhw.atk2); //Else use Atk2 + break; + case RK_DRAGONBREATH: + case RK_DRAGONBREATH_WATER: + { + int damagevalue = (sstatus->hp / 50 + status_get_max_sp(src) / 4) * skill_lv; + if(status_get_lv(src) > 100) + damagevalue = damagevalue * status_get_lv(src) / 100; + if(sd) { + if (pc_checkskill( sd, DK_DRAGONIC_AURA ) >= 1) { + damagevalue = damagevalue * (90 + 10 * pc_checkskill( sd, RK_DRAGONTRAINING ) + sstatus->pow / 5 ) / 100; + } else { + damagevalue = damagevalue * (90 + 10 * pc_checkskill( sd, RK_DRAGONTRAINING )) / 100; + } + } + if (sc && sc->getSCE(SC_DRAGONIC_AURA)) + damagevalue += damagevalue * sc->getSCE(SC_DRAGONIC_AURA)->val1 * 10 / 100; + ATK_ADD(wd->damage, wd->damage2, damagevalue); +#ifdef RENEWAL + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue); +#endif + wd->flag |= BF_LONG; + } + break; + case NC_SELFDESTRUCTION: { + int damagevalue = (skill_lv + 1) * ((sd ? pc_checkskill(sd,NC_MAINFRAME) : 0) + 8) * (status_get_sp(src) + sstatus->vit); + + if(status_get_lv(src) > 100) + damagevalue = damagevalue * status_get_lv(src) / 100; + damagevalue = damagevalue + sstatus->hp; + ATK_ADD(wd->damage, wd->damage2, damagevalue); +#ifdef RENEWAL + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue); +#endif + } + break; + case KO_HAPPOKUNAI: + if(sd) { + short index = sd->equip_index[EQI_AMMO]; + int damagevalue = 3 * ( +#ifdef RENEWAL + 2 * +#endif + sstatus->batk + sstatus->rhw.atk + (index >= 0 && sd->inventory_data[index] ? + sd->inventory_data[index]->atk : 0)) * (skill_lv + 5) / 5; + if (sc && sc->getSCE(SC_KAGEMUSYA)) + damagevalue += damagevalue * sc->getSCE(SC_KAGEMUSYA)->val2 / 100; + ATK_ADD(wd->damage, wd->damage2, damagevalue); +#ifdef RENEWAL + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, damagevalue); +#endif + } else + ATK_ADD(wd->damage, wd->damage2, 5000); + break; + case HFLI_SBR44: //[orn] + if(src->type == BL_HOM) + wd->damage = ((TBL_HOM*)src)->homunculus.intimacy ; + break; + + default: + // Flags that apply to both pre-renewal and renewal + bflag = BDMG_NONE; + if (is_attack_critical(wd, src, target, skill_id, skill_lv, false)) bflag |= BDMG_CRIT; + if (!skill_id && sc && sc->getSCE(SC_CHANGE)) bflag |= BDMG_MAGIC; +#ifdef RENEWAL + if (sd) + battle_calc_damage_parts(wd, src, target, skill_id, skill_lv); + else { + wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, bflag); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 = battle_calc_base_damage(src, sstatus, &sstatus->lhw, sc, tstatus->size, bflag); + } +#else + // Pre-renewal exclusive flags + if (skill_id == HW_MAGICCRASHER) bflag |= BDMG_MAGIC; + if (sc && sc->getSCE(SC_WEAPONPERFECTION)) bflag |= BDMG_NOSIZE; + if (is_skill_using_arrow(src, skill_id) && sd) { + switch(sd->status.weapon) { + case W_BOW: + bflag |= BDMG_ARROW; + break; + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + case W_GRENADE: + // Criticals with any guns are calculated like melee criticals + if (!(bflag & BDMG_CRIT)) { + bflag |= BDMG_ARROW; + } + break; + default: + // Attacks that use ammo are calculated like melee attacks as long as no ranged weapon is equipped + // Some skills manually add arrow_atk as mastery bonus later (e.g. Throw Shuriken, Throw Kunai) + break; + } + } + if (skill_id == SN_SHARPSHOOTING || skill_id == MA_SHARPSHOOTING) + bflag &= ~(BDMG_CRIT); // Sharpshooting just ignores DEF/FLEE but damage is like a normal attack + wd->damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, bflag); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 = battle_calc_base_damage(src, sstatus, &sstatus->lhw, sc, tstatus->size, bflag); +#endif + if (nk[NK_SPLASHSPLIT]){ // Divide ATK among targets + if(wd->miscflag > 0) { + wd->damage /= wd->miscflag; +#ifdef RENEWAL + wd->statusAtk /= wd->miscflag; + wd->weaponAtk /= wd->miscflag; + wd->equipAtk /= wd->miscflag; + wd->masteryAtk /= wd->miscflag; +#endif + } 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 atk (pre-skills) + if(sd) { + int skill; + +#ifndef RENEWAL + if (sd->bonus.crit_atk_rate && (bflag & BDMG_CRIT)) { // add +crit damage bonuses here in pre-renewal mode [helvetica] + ATK_ADDRATE(wd->damage, wd->damage2, 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] + // Reduce count by one (self) [Tydus1] + i -= 1; + ATK_ADDRATE(wd->damage, wd->damage2, 2*skill*i); + } + } +#else + if ((skill = pc_checkskill(sd, TK_POWER)) > 0) { + ATK_ADDRATE(wd->damage, wd->damage2, 10 + 15 * skill); + RE_ALLATK_ADDRATE(wd, 10 + 15 * skill); + } +#endif + } +#ifndef RENEWAL + if (tsd != nullptr && tsd->bonus.crit_def_rate != 0 && !skill_id && (bflag & BDMG_CRIT)) { + ATK_ADDRATE(wd->damage, wd->damage2, -tsd->bonus.crit_def_rate); + } + //Acid Terror ignores DEF but will substract VIT from base attack value instead + if (skill_id == AM_ACIDTERROR) + ATK_ADD(wd->damage, wd->damage2, -tstatus->def2); +#endif + break; + } //End switch(skill_id) +} + +//For quick div adjustment. +#define DAMAGE_DIV_FIX(dmg, div) { if ((div) < 0) { (div) *= -1; (dmg) /= (div); } (dmg) *= (div); } +#define DAMAGE_DIV_FIX2(dmg, div) { if ((div) > 1) (dmg) *= div; } + +/*================================================= [Playtester] + * Applies DAMAGE_DIV_FIX and checks for min damage + * @param d: Damage struct to apply DAMAGE_DIV_FIX to + * @param skill_id: ID of the skill that deals damage + * @return Modified damage struct + *------------------------------------------------*/ +static void battle_apply_div_fix(struct Damage* d, uint16 skill_id) +{ + if(d->damage) { + DAMAGE_DIV_FIX(d->damage, d->div_); + //Min damage + if(d->damage < d->div_ && (skill_id == SU_LUNATICCARROTBEAT || skill_id == SU_LUNATICCARROTBEAT2 || skill_id == SU_CN_METEOR || skill_id == SU_CN_METEOR2 || (battle_config.skill_min_damage&d->flag))) + d->damage = d->div_; + } else if (d->div_ < 0) { + d->div_ *= -1; + } +} + +/*======================================= + * Check for and calculate multi attacks + *--------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_multi_attack(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + status_data* tstatus = status_get_status_data(*target); + + if( sd && !skill_id ) { // if no skill_id passed, check for double attack [helvetica] + short i; + if(sc && sc->getSCE(SC_FEARBREEZE) && sd->weapontype1==W_BOW + && (i = sd->equip_index[EQI_AMMO]) >= 0 && sd->inventory_data[i] && sd->inventory.u.items_inventory[i].amount > 1) + { + int chance = rnd()%100; + switch(sc->getSCE(SC_FEARBREEZE)->val1) { + case 5: + if (chance < 4) { + wd->div_ = 5; + break; + } // 3 % chance to attack 5 times. + [[fallthrough]]; + case 4: + if (chance < 7) { + wd->div_ = 4; + break; + } // 6 % chance to attack 4 times. + [[fallthrough]]; + case 3: + if (chance < 10) { + wd->div_ = 3; + break; + } // 9 % chance to attack 3 times. + [[fallthrough]]; + case 2: + case 1: + if (chance < 13) { + wd->div_ = 2; + break; + } // 12 % chance to attack 2 times. + } + wd->div_ = min(wd->div_,sd->inventory.u.items_inventory[i].amount); + sc->getSCE(SC_FEARBREEZE)->val4 = wd->div_-1; + if (wd->div_ > 1) + wd->type = DMG_MULTI_HIT; + } + if( wd->div_ == 1 && ( ( skill_lv = pc_checkskill(sd,TF_DOUBLE) ) > 0 && sd->weapontype1 == W_DAGGER ) + || ( pc_checkskill_flag(*sd, TF_DOUBLE) > SKILL_FLAG_PERMANENT && sd->weapontype1 != W_FIST ) + || ( sd->bonus.double_rate > 0 && sd->weapontype1 != W_FIST ) // Will fail bare-handed + || ( sc && sc->getSCE(SC_KAGEMUSYA) && sd->weapontype1 != W_FIST )) // Will fail bare-handed + { //Success chance is not added, the higher one is used [Skotlex] + int max_rate = 0; + + if (sc && sc->getSCE(SC_KAGEMUSYA)) + max_rate = sc->getSCE(SC_KAGEMUSYA)->val1 * 10; // Same rate as even levels of TF_DOUBLE + else +#ifdef RENEWAL + max_rate = max(7 * skill_lv, sd->bonus.double_rate); +#else + max_rate = max(5 * skill_lv, sd->bonus.double_rate); +#endif + + if( rnd()%100 < max_rate ) { + wd->div_ = skill_get_num(TF_DOUBLE,skill_lv?skill_lv:1); + wd->type = DMG_MULTI_HIT; + } + } + if( wd->div_ == 1 && ((sd->weapontype1 == W_REVOLVER && (skill_lv = pc_checkskill(sd,GS_CHAINACTION)) > 0) //Normal Chain Action effect + || (sc && sc->count && sc->getSCE(SC_E_CHAIN) && (skill_lv = sc->getSCE(SC_E_CHAIN)->val1) > 0)) //Chain Action of ETERNAL_CHAIN + && rnd()%100 < 5*skill_lv ) //Success rate + { + wd->div_ = skill_get_num(GS_CHAINACTION,skill_lv); + wd->type = DMG_MULTI_HIT; + + sc_start(src,src,SC_QD_SHOT_READY,100,target->id,skill_get_time(RL_QD_SHOT,1)); + } + } + + switch (skill_id) { + case RK_WINDCUTTER: + if (sd && sd->weapontype1 == W_2HSWORD) + wd->div_ = 2; + break; + case SC_FATALMENACE: + if (sd && sd->weapontype1 == W_DAGGER) + wd->div_++; + break; + case SR_RIDEINLIGHTNING: + wd->div_ = (sd ? max(1, skill_lv) : 1); + break; + case RL_QD_SHOT: + wd->div_ = 1 + (sd ? sd->status.job_level : 1) / 20 + (tsc && tsc->getSCE(SC_C_MARKER) ? 2 : 0); + break; + case KO_JYUMONJIKIRI: + if( tsc && tsc->getSCE(SC_JYUMONJIKIRI) ) + wd->div_ = wd->div_ * -1;// needs more info + break; + case MH_BLAZING_AND_FURIOUS: { + struct homun_data *hd = BL_CAST(BL_HOM, src); + if (hd) { + wd->div_ = hd->homunculus.spiritball; + hom_delspiritball(hd, MAX_SPIRITBALL, 1); + } + break; + } + case ABC_FRENZY_SHOT: + if( rnd_chance( 5 * skill_lv, 100 ) ){ + wd->div_ = 3; + } + break; +#ifdef RENEWAL + case AS_POISONREACT: + skill_lv = pc_checkskill(sd, TF_DOUBLE); + if (skill_lv > 0) { + if(rnd()%100 < (7 * skill_lv)) { + wd->div_++; + } + } + break; +#endif + case NW_SPIRAL_SHOOTING: + if (sd && sd->weapontype1 == W_GRENADE) + wd->div_ += 1; + break; + case NW_MAGAZINE_FOR_ONE: + if (sd && sd->weapontype1 == W_GATLING) + wd->div_ += 4; + break; + case NW_THE_VIGILANTE_AT_NIGHT: + if (sd && sd->weapontype1 == W_GATLING) + wd->div_ += 3; + break; + } +} + +/** + * Calculates the percentual attack modificator (ATKpercent) based on status changes + * These bonuses are added together and the percent is applied to the damage before the defense reduction in pre-renewal + * In renewal this simply sets the base skillratio before the actual skill ratio of the skill used is added + * This bonus works as a separate unit to the rest (e.g., if one of these is not applied to a skill, then we know none are) + * Do not add additional status changes here unless they are confirmed to use ATKpercent + * @param bl: Object to calc atkpercent for + * @param skill_id: Skill used by object + * @param sc: Object's status change information + * @return atkpercent with cap_value(watk,0,USHRT_MAX) + */ +static unsigned short battle_get_atkpercent(struct block_list& bl, uint16 skill_id, status_change& sc) +{ + if (bl.type == BL_PC && !battle_skill_stacks_masteries_vvs(skill_id, BCHK_ALL)) + return 100; + + int atkpercent = 100; + + if (sc.getSCE(SC_CURSE)) + atkpercent -= 25; + if (sc.getSCE(SC_PROVOKE)) + atkpercent += sc.getSCE(SC_PROVOKE)->val2; + if (sc.getSCE(SC_STRIPWEAPON) && bl.type != BL_PC) + atkpercent -= sc.getSCE(SC_STRIPWEAPON)->val2; + if (sc.getSCE(SC_CONCENTRATION)) + atkpercent += sc.getSCE(SC_CONCENTRATION)->val2; + if (sc.getSCE(SC_TRUESIGHT)) + atkpercent += 2 * sc.getSCE(SC_TRUESIGHT)->val1; + if (sc.getSCE(SC_JOINTBEAT) && sc.getSCE(SC_JOINTBEAT)->val2 & BREAK_WAIST) + atkpercent -= 25; + if (sc.getSCE(SC_INCATKRATE)) + atkpercent += sc.getSCE(SC_INCATKRATE)->val1; + if (sc.getSCE(SC_POWERUP)) + atkpercent += sc.getSCE(SC_POWERUP)->val1; + if (sc.getSCE(SC_SKE)) + atkpercent += 300; + if (sc.getSCE(SC_BLOODLUST)) + atkpercent += sc.getSCE(SC_BLOODLUST)->val2; + if (sc.getSCE(SC_FLEET)) + atkpercent += sc.getSCE(SC_FLEET)->val3; + if (sc.getSCE(SC_INVINCIBLE)) + atkpercent += sc.getSCE(SC_INVINCIBLE)->val2; + + /* Only few selected skills should use this function, DO NOT ADD any that are not caused by the skills listed below + * TODO: + * GD_GUARDUP (2*skLevel+8) + * EL_WATERBARRIER (-3) + * SC_ENERVATION (-30/-40/-50) + * MH_EQC (skLevel*5) + * MH_VOLCANIC_ASH (-50) + */ + + return (unsigned short)cap_value(atkpercent, 0, USHRT_MAX); +} + +/*====================================================== + * Calculate skill level ratios for weapon-based skills + *------------------------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static int battle_calc_attack_skill_ratio(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + map_session_data *tsd = BL_CAST(BL_PC, target); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + int skillratio = 100; + int i; + + //Skill damage modifiers that stack linearly + if(sc && skill_id != PA_SACRIFICE) { +#ifdef RENEWAL + //ATK percent modifier (in renewal, it's applied before the skillratio) + skillratio = battle_get_atkpercent(*src, skill_id, *sc); +#endif + if(sc->getSCE(SC_OVERTHRUST)) + skillratio += sc->getSCE(SC_OVERTHRUST)->val3; + if(sc->getSCE(SC_MAXOVERTHRUST)) + skillratio += sc->getSCE(SC_MAXOVERTHRUST)->val2; + if(sc->getSCE(SC_BERSERK)) +#ifndef RENEWAL + skillratio += 100; +#else + skillratio += 200; +#endif + if (!skill_id || skill_id == KN_AUTOCOUNTER) { + if (sc->getSCE(SC_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 += -100 + sd->inventory_data[index]->weight / 10 + sd->inventory_data[index]->atk + + 100 * sd->inventory_data[index]->weapon_level * (sd->inventory.u.items_inventory[index].refine + 6); + } + status_change_end(src,SC_CRUSHSTRIKE); + skill_break_equip(src,src,EQP_WEAPON,2000,BCT_SELF); + } else { + if (sc->getSCE(SC_GIANTGROWTH) && (sd->class_&MAPID_THIRDMASK) == MAPID_RUNE_KNIGHT) { // Increase damage again if Crush Strike is not active + if (map_flag_vs(src->m)) // Only half of the 2.5x increase on versus-type maps + skillratio += 125; + else + skillratio += 250; + } + } + } + } + + switch(skill_id) { + case SM_BASH: + case MS_BASH: + skillratio += 30 * skill_lv; + break; + case SM_MAGNUM: + case MS_MAGNUM: + if(wd->miscflag == 1) + skillratio += 20 * skill_lv; //Inner 3x3 circle takes 100%+20%*level damage [Playtester] + else + skillratio += 10 * skill_lv; //Outer 5x5 circle takes 100%+10%*level damage [Playtester] + 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; + case KN_PIERCE: + skillratio += 10 * skill_lv; + if (sc && sc->getSCE(SC_CHARGINGPIERCE_COUNT) && sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 >= 10) + skillratio *= 2; + break; + case ML_PIERCE: + skillratio += 10 * skill_lv; + break; + case MER_CRASH: + skillratio += 10 * skill_lv; + break; + case KN_SPEARSTAB: + skillratio += 20 * skill_lv; + break; + case KN_SPEARBOOMERANG: + skillratio += 50 * skill_lv; + break; +#ifdef RENEWAL + case KN_BRANDISHSPEAR: + skillratio += -100 + 400 + 100 * skill_lv + sstatus->str * 3; + break; +#else + case KN_BRANDISHSPEAR: +#endif + case ML_BRANDISH: + { + int ratio = 100 + 20 * skill_lv; + + skillratio += -100 + ratio; + if(skill_lv > 3 && wd->miscflag == 0) + skillratio += ratio / 2; + if(skill_lv > 6 && wd->miscflag == 0) + skillratio += ratio / 4; + if(skill_lv > 9 && wd->miscflag == 0) + skillratio += ratio / 8; + if(skill_lv > 6 && wd->miscflag == 1) + skillratio += ratio / 2; + if(skill_lv > 9 && wd->miscflag == 1) + skillratio += ratio / 4; + if(skill_lv > 9 && wd->miscflag == 2) + 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: +#ifdef RENEWAL + skillratio += 100 + 100 * skill_lv; + if (tstatus->hp < (tstatus->max_hp / 2)) + skillratio += skillratio / 2; +#else + skillratio += 200 + 50 * skill_lv; + if (sd && pc_checkskill(sd, AS_SONICACCEL) > 0) + skillratio += skillratio / 10; +#endif + 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_PIERCINGATT: + skillratio += -25; //75% base damage + break; + case NPC_COMBOATTACK: + skillratio += 25 * skill_lv; + break; + 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_UNDEADATTACK: + case NPC_TELEKINESISATTACK: + case NPC_BLOODDRAIN: + case NPC_ACIDBREATH: + case NPC_DARKNESSBREATH: + case NPC_FIREBREATH: + case NPC_ICEBREATH: + case NPC_ICEBREATH2: + case NPC_THUNDERBREATH: + case NPC_HELLJUDGEMENT: + case NPC_HELLJUDGEMENT2: + case NPC_PULSESTRIKE: + skillratio += 100 * (skill_lv - 1); + break; + case NPC_REVERBERATION_ATK: + skillratio += 400 + 200 * skill_lv; + 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: +#ifdef RENEWAL + skillratio += -100 + 50 + skill_lv * 150; +#else + skillratio += 40 * skill_lv; +#endif + break; + case RG_INTIMIDATE: + skillratio += 30 * skill_lv; + break; + case CR_SHIELDCHARGE: + skillratio += 20 * skill_lv; + break; + case CR_SHIELDBOOMERANG: +#ifdef RENEWAL + skillratio += -100 + skill_lv * 80; +#else + skillratio += 30 * skill_lv; +#endif + break; + case NPC_DARKCROSS: + case CR_HOLYCROSS: +#ifdef RENEWAL + if(sd && sd->status.weapon == W_2HSPEAR) + skillratio += 70 * skill_lv; + else +#endif + skillratio += 35 * skill_lv; + break; + case AM_DEMONSTRATION: + skillratio += 20 * skill_lv; + break; + case AM_ACIDTERROR: +#ifdef RENEWAL + skillratio += -100 + 200 * skill_lv; + if (sd && pc_checkskill(sd, AM_LEARNINGPOTION)) + skillratio += 100; // !TODO: What's this bonus increase? +#else + skillratio += -50 + 50 * skill_lv; +#endif + break; + case MO_FINGEROFFENSIVE: +#ifdef RENEWAL + skillratio += 500 + skill_lv * 200; + if (tsc && tsc->getSCE(SC_BLADESTOP)) + skillratio += skillratio / 2; +#else + skillratio += 50 * skill_lv; +#endif + break; + case MO_INVESTIGATE: +#ifdef RENEWAL + skillratio += -100 + 100 * skill_lv; + if (tsc && tsc->getSCE(SC_BLADESTOP)) + skillratio += skillratio / 2; +#else + skillratio += 75 * skill_lv; +#endif + break; + case MO_EXTREMITYFIST: + skillratio += 700 + sstatus->sp * 10; +#ifdef RENEWAL + if (wd->miscflag&1) + skillratio *= 2; // More than 5 spirit balls active +#endif + skillratio = min(500000,skillratio); //We stop at roughly 50k SP for overflow protection + break; + case MO_TRIPLEATTACK: + skillratio += 20 * skill_lv; + break; + case MO_CHAINCOMBO: +#ifdef RENEWAL + skillratio += 150 + 50 * skill_lv; + if (sd && sd->status.weapon == W_KNUCKLE) + skillratio *= 2; +#else + skillratio += 50 + 50 * skill_lv; +#endif + break; + case MO_COMBOFINISH: +#ifdef RENEWAL + skillratio += 450 + 50 * skill_lv + sstatus->str; // !TODO: How does STR play a role? +#else + skillratio += 140 + 60 * skill_lv; +#endif + if (sc->getSCE(SC_GT_ENERGYGAIN)) + skillratio += skillratio * 50 / 100; + break; + case BA_MUSICALSTRIKE: + case DC_THROWARROW: +#ifdef RENEWAL + skillratio += 10 + 40 * skill_lv; +#else + skillratio += 25 + 25 * skill_lv; +#endif + break; + case CH_TIGERFIST: +#ifdef RENEWAL + skillratio += 400 + 150 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += -60 + 100 * skill_lv; +#endif + if (sc->getSCE(SC_GT_ENERGYGAIN)) + skillratio += skillratio * 50 / 100; + break; + case CH_CHAINCRUSH: +#ifdef RENEWAL + skillratio += -100 + 200 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += 300 + 100 * skill_lv; +#endif + if (sc->getSCE(SC_GT_ENERGYGAIN)) + skillratio += skillratio * 50 / 100; + break; + case CH_PALMSTRIKE: +#ifdef RENEWAL + skillratio += 100 + 100 * skill_lv + sstatus->str; // !TODO: How does STR play a role? + RE_LVL_DMOD(100); +#else + skillratio += 100 + 100 * skill_lv; +#endif + break; + case LK_HEADCRUSH: + skillratio += 40 * skill_lv; + break; + case LK_JOINTBEAT: + skillratio += 10 * skill_lv - 50; + if (wd->miscflag & BREAK_NECK || (tsc && tsc->getSCE(SC_JOINTBEAT) && tsc->getSCE(SC_JOINTBEAT)->val2 & BREAK_NECK)) // The 2x damage is only for the BREAK_NECK ailment. + skillratio *= 2; + break; +#ifdef RENEWAL + // Renewal: skill ratio applies to entire damage [helvetica] + case LK_SPIRALPIERCE: + skillratio += 50 + 50 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CHARGINGPIERCE_COUNT) && sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 >= 10) + skillratio *= 2; + break; + case ML_SPIRALPIERCE: + skillratio += 50 + 50 * skill_lv; + RE_LVL_DMOD(100); + break; +#endif + case ASC_METEORASSAULT: +#ifdef RENEWAL + skillratio += 100 + 120 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += -60 + 40 * skill_lv; +#endif + break; + case SN_SHARPSHOOTING: + if (src->type == BL_MOB) { // TODO: Did these formulas change in the renewal balancing? + if (wd->miscflag & 2) // Splash damage bonus + skillratio += -100 + 140 * skill_lv; + else + skillratio += 100 + 50 * skill_lv; + break; + } + [[fallthrough]]; + case MA_SHARPSHOOTING: +#ifdef RENEWAL + skillratio += -100 + 300 + 300 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += 100 + 50 * skill_lv; +#endif + break; +#ifdef RENEWAL + case CR_ACIDDEMONSTRATION: + skillratio += -100 + 200 * skill_lv + sstatus->int_ + tstatus->vit; // !TODO: Confirm status bonus + if (target->type == BL_PC) + skillratio /= 2; + break; +#endif + case CG_ARROWVULCAN: +#ifdef RENEWAL + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += 100 + 100 * skill_lv; +#endif + break; + case AS_SPLASHER: +#ifdef RENEWAL + skillratio += -100 + 400 + 100 * skill_lv; +#else + skillratio += 400 + 50 * skill_lv; +#endif + if(sd) + skillratio += 20 * pc_checkskill(sd,AS_POISONREACT); + break; + case ASC_BREAKER: +#ifdef RENEWAL + skillratio += -100 + 150 * skill_lv + sstatus->str + sstatus->int_; // !TODO: Confirm stat modifier + RE_LVL_DMOD(100); +#else + // Pre-Renewal: skill ratio for weapon part of damage [helvetica] + skillratio += -100 + 100 * skill_lv; +#endif + break; + case PA_SACRIFICE: + skillratio += -10 + 10 * skill_lv; + break; + case PA_SHIELDCHAIN: +#ifdef RENEWAL + skillratio = -100 + 300 + 200 * skill_lv; + + if( sd != nullptr ){ + int16 index = sd->equip_index[EQI_HAND_L]; + + // Damage affected by the shield's weight and refine. + if( index >= 0 && sd->inventory_data[index] != nullptr && sd->inventory_data[index]->type == IT_ARMOR ){ + skillratio += sd->inventory_data[index]->weight / 10 + 4 * sd->inventory.u.items_inventory[index].refine; + } + + // Damage affected by shield mastery + if( sc != nullptr && sc->getSCE( SC_SHIELD_POWER ) ){ + skillratio += skill_lv * 14 * pc_checkskill( sd, IG_SHIELD_MASTERY ); + } + } + + RE_LVL_DMOD(100); +#else + skillratio += 30 * skill_lv; +#endif + if (sc && sc->getSCE(SC_SHIELD_POWER))// Whats the official increase? [Rytech] + skillratio += skillratio * 50 / 100; + 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: + case TK_STORMKICK: + skillratio += 60 + 20 * skill_lv; + break; + case TK_TURNKICK: + case TK_COUNTER: + skillratio += 90 + 30 * skill_lv; + break; + case TK_JUMPKICK: + //Different damage formulas depending on damage trigger + if (sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == skill_id) + skillratio += -100 + 4 * status_get_lv(src); //Tumble formula [4%*baselevel] + else if (wd->miscflag) { + skillratio += -100 + 4 * status_get_lv(src); //Running formula [4%*baselevel] + if (sc && sc->getSCE(SC_SPURT)) //Spurt formula [8%*baselevel] + skillratio *= 2; + } + else + skillratio += -70 + 10 * skill_lv; + 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->race == RC_PLAYER_HUMAN || tstatus->race == RC_PLAYER_DORAM) && !status_has_mode(tstatus,MD_STATUSIMMUNE)) + skillratio += 400; + break; + case GS_TRACKING: + skillratio += 100 * (skill_lv + 1); + break; + case GS_PIERCINGSHOT: +#ifdef RENEWAL + if (sd && sd->weapontype1 == W_RIFLE) + skillratio += 150 + 30 * skill_lv; + else + skillratio += 100 + 20 * skill_lv; +#else + skillratio += 20 * skill_lv; +#endif + break; + case GS_RAPIDSHOWER: + skillratio += 400 + 50 * skill_lv; + break; + case GS_DESPERADO: + skillratio += 50 * (skill_lv - 1); + if (sc && sc->getSCE(SC_FALLEN_ANGEL)) + skillratio *= 2; + break; + case GS_DUST: + skillratio += 50 * skill_lv; + break; + case GS_FULLBUSTER: + skillratio += 100 * (skill_lv + 2); + break; + case GS_SPREADATTACK: +#ifdef RENEWAL + skillratio += 30 * skill_lv; +#else + skillratio += 20 * (skill_lv - 1); +#endif + break; +#ifdef RENEWAL + case GS_GROUNDDRIFT: + skillratio += 100 + 20 * skill_lv; + break; +#endif + case NJ_HUUMA: +#ifdef RENEWAL + skillratio += -150 + 250 * skill_lv; +#else + skillratio += 50 + 150 * skill_lv; +#endif + break; + case NJ_TATAMIGAESHI: + skillratio += 10 * skill_lv; +#ifdef RENEWAL + skillratio *= 2; +#endif + break; + case NJ_KASUMIKIRI: +#ifdef RENEWAL + skillratio += 20 * skill_lv; +#else + skillratio += 10 * skill_lv; +#endif + break; + case NJ_KIRIKAGE: +#ifdef RENEWAL + skillratio += -50 + 150 * skill_lv; +#else + skillratio += 100 * (skill_lv - 1); +#endif + break; +#ifdef RENEWAL + case NJ_SYURIKEN: + skillratio += 5 * skill_lv; + break; + case NJ_KUNAI: + skillratio += -100 + 100 * skill_lv; + break; + case KN_CHARGEATK: + skillratio += 600; + break; + case AS_VENOMKNIFE: + skillratio += 400; + break; +#else + case KN_CHARGEATK: { // +100% every 3 cells of distance but hard-limited to 500% + int k = (wd->miscflag-1)/3; + if (k < 0) + k = 0; + else if (k > 4) + k = 4; + skillratio += 100 * k; + } + break; +#endif + case HT_PHANTASMIC: +#ifdef RENEWAL + skillratio += 400; +#else + skillratio += 50; +#endif + break; + case MO_BALKYOUNG: +#ifdef RENEWAL + skillratio += 700; +#else + skillratio += 200; +#endif + 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 += -100 + 1050 + 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case RK_HUNDREDSPEAR: + skillratio += -100 + 600 + 200 * skill_lv; + if (sd) + skillratio += 50 * pc_checkskill(sd,LK_SPIRALPIERCE); + if (sc) { + if( sc->getSCE( SC_DRAGONIC_AURA ) ){ + skillratio += sc->getSCE( SC_DRAGONIC_AURA )->val1 * 160; + } + + if (sc->getSCE(SC_CHARGINGPIERCE_COUNT) && sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 >= 10) + skillratio *= 2; + } + RE_LVL_DMOD(100); + break; + case RK_WINDCUTTER: + if (sd) { + if (sd->weapontype1 == W_2HSWORD) + skillratio += -100 + 250 * skill_lv; + else if (sd->weapontype1 == W_1HSPEAR || sd->weapontype1 == W_2HSPEAR) + skillratio += -100 + 400 * skill_lv; + else + skillratio += -100 + 300 * skill_lv; + } else + skillratio += -100 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case RK_IGNITIONBREAK: + skillratio += -100 + 450 * skill_lv; + RE_LVL_DMOD(100); + break; + case NPC_IGNITIONBREAK: + // 3x3 cell Damage = 1000 1500 2000 2500 3000 % + // 7x7 cell Damage = 750 1250 1750 2250 2750 % + // 11x11 cell Damage = 500 1000 1500 2000 2500 % + i = distance_bl(src,target); + if (i < 2) + skillratio += -100 + 500 * (skill_lv + 1); + else if (i < 4) + skillratio += -100 + 250 + 500 * skill_lv; + else + skillratio += -100 + 500 * skill_lv; + break; + case RK_STORMBLAST: + skillratio += -100 + (((sd) ? pc_checkskill(sd,RK_RUNEMASTERY) : 0) + sstatus->str / 6) * 100; // ATK = [{Rune Mastery Skill Level + (Caster's STR / 6)} x 100] % + RE_LVL_DMOD(100); + break; + case RK_PHANTOMTHRUST: // ATK = [{(Skill Level x 50) + (Spear Master Level x 10)} x Caster's Base Level / 150] % + skillratio += -100 + 50 * skill_lv + 10 * (sd ? pc_checkskill(sd,KN_SPEARMASTERY) : 5); + RE_LVL_DMOD(150); // Base level bonus. + break; + // case NPC_PHANTOMTHRUST: // ATK = 100% for all level + case GC_CROSSIMPACT: + skillratio += -100 + 1400 + 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case GC_COUNTERSLASH: + //ATK [{(Skill Level x 150) + 300} x Caster's Base Level / 120]% + ATK [(AGI x 2) + (Caster's Job Level x 4)]% + skillratio += -100 + 300 + 150 * skill_lv; + RE_LVL_DMOD(120); + skillratio += sstatus->agi * 2; + // If 4th job, job level of your 3rd job counts + skillratio += (sd ? (sd->class_&JOBL_FOURTH ? sd->change_level_4th : sd->status.job_level) * 4 : 0); + break; + case GC_VENOMPRESSURE: + skillratio += 900; + break; + case GC_PHANTOMMENACE: + skillratio += 200; + break; + case GC_ROLLINGCUTTER: + skillratio += -100 + 50 + 80 * skill_lv; + RE_LVL_DMOD(100); + break; + case GC_CROSSRIPPERSLASHER: + skillratio += -100 + 80 * skill_lv + (sstatus->agi * 3); + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_ROLLINGCUTTER)) + skillratio += sc->getSCE(SC_ROLLINGCUTTER)->val1 * 200; + break; + case GC_DARKCROW: + skillratio += 100 * (skill_lv - 1); + break; + case AB_DUPLELIGHT_MELEE: + skillratio += 50 + 15 * skill_lv; + break; + case NPC_ARROWSTORM: + if (skill_lv > 4) + skillratio += 1900; + else + skillratio += 900; + break; + case NPC_DRAGONBREATH: + if (skill_lv > 5) + skillratio += 500 + 500 * (skill_lv - 5); // Level 6-10 is using water element, like RK_DRAGONBREATH_WATER + else + skillratio += 500 + 500 * skill_lv; // Level 1-5 is using fire element, like RK_DRAGONBREATH + break; + case RA_ARROWSTORM: + if (sc && sc->getSCE(SC_FEARBREEZE)) + skillratio += -100 + 200 + 250 * skill_lv; + else + skillratio += -100 + 200 + 180 * skill_lv; + RE_LVL_DMOD(100); + break; + case RA_AIMEDBOLT: + if (sc && sc->getSCE(SC_FEARBREEZE)) + skillratio += -100 + 800 + 35 * skill_lv; + else + skillratio += -100 + 500 + 20 * skill_lv; + RE_LVL_DMOD(100); + break; + case RA_CLUSTERBOMB: + skillratio += 100 + 100 * skill_lv; + break; + case RA_WUGDASH:// ATK 300% + skillratio += 200; + break; + case RA_WUGSTRIKE: + skillratio += -100 + 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; + case NC_BOOSTKNUCKLE: + skillratio += -100 + 260 * skill_lv + sstatus->dex; // !TODO: What's the DEX bonus? + RE_LVL_DMOD(100); + break; + case NC_PILEBUNKER: + skillratio += 200 + 100 * skill_lv + status_get_str(src); + RE_LVL_DMOD(100); + break; + case NC_VULCANARM: + skillratio += -100 + 230 * skill_lv + sstatus->dex; // !TODO: What's the DEX bonus? + RE_LVL_DMOD(100); + break; + case NC_FLAMELAUNCHER: + case NC_COLDSLOWER: + skillratio += 200 + 300 * skill_lv; + RE_LVL_DMOD(150); + break; + case NC_ARMSCANNON: + skillratio += -100 + 400 + 350 * skill_lv; + RE_LVL_DMOD(100); + break; + case NC_AXEBOOMERANG: + skillratio += 150 + 50 * 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 actual weight. [Rytech] + } + RE_LVL_DMOD(100); + break; + case NC_POWERSWING: // According to current sources, only the str + dex gets modified by level [Akinari] + skillratio += -100 + ((sstatus->str + sstatus->dex)/ 2) + 300 + 100 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_ABR_BATTLE_WARIOR)) + skillratio *= 2; + break; + case NC_MAGMA_ERUPTION: // 'Slam' damage + skillratio += 350 + 50 * skill_lv; + break; + case NC_AXETORNADO: + skillratio += -100 + 200 + 180 * skill_lv + sstatus->vit * 2; + if (sc && sc->getSCE(SC_AXE_STOMP)) + skillratio += 380; + RE_LVL_DMOD(100); + break; + case SC_FATALMENACE: + skillratio += 120 * skill_lv + sstatus->agi; // !TODO: What's the AGI bonus? + + if( sc != nullptr && sc->getSCE( SC_ABYSS_DAGGER ) ){ + skillratio += 30 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case SC_TRIANGLESHOT: + skillratio += -100 + 230 * skill_lv + 3 * sstatus->agi; + RE_LVL_DMOD(100); + break; + case SC_FEINTBOMB: + skillratio += -100 + (skill_lv + 1) * sstatus->dex / 2 * ((sd) ? sd->status.job_level / 10 : 1); + RE_LVL_DMOD(120); + break; + case LG_CANNONSPEAR: + skillratio += -100 + skill_lv * ( 120 + sstatus->str ); + + if( sc != nullptr && sc->getSCE( SC_SPEAR_SCAR ) ){ + skillratio += 400; + } + + RE_LVL_DMOD(100); + break; + case LG_BANISHINGPOINT: + skillratio += -100 + ( 100 * skill_lv ); + + if( sd != nullptr ){ + skillratio += pc_checkskill( sd, SM_BASH ) * 70; + } + + if( sc != nullptr && sc->getSCE( SC_SPEAR_SCAR ) ){ + skillratio += 800; + } + + RE_LVL_DMOD(100); + break; + case LG_SHIELDPRESS: + skillratio += -100 + 200 * skill_lv; + if (sd) { + // Shield Press only considers base STR without job bonus + skillratio += sd->status.str; + + if( sc != nullptr && sc->getSCE( SC_SHIELD_POWER ) ){ + skillratio += skill_lv * 15 * pc_checkskill( sd, IG_SHIELD_MASTERY ); + } + + short index = sd->equip_index[EQI_HAND_L]; + + if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) + skillratio += sd->inventory_data[index]->weight / 10; + } + RE_LVL_DMOD(100); + break; + case LG_PINPOINTATTACK: + skillratio += -100 + 100 * skill_lv + 5 * status_get_agi(src); + RE_LVL_DMOD(120); + break; + case LG_RAGEBURST: + if (sd && sd->spiritball_old) + skillratio += -100 + 200 * sd->spiritball_old + (status_get_max_hp(src) - status_get_hp(src)) / 100; + else + skillratio += 2900 + (status_get_max_hp(src) - status_get_hp(src)); + RE_LVL_DMOD(100); + break; + case LG_MOONSLASHER: + skillratio += -100 + 120 * skill_lv + ((sd) ? pc_checkskill(sd,LG_OVERBRAND) * 80 : 0); + RE_LVL_DMOD(100); + break; + case LG_OVERBRAND: + if(sc && sc->getSCE(SC_OVERBRANDREADY)) + skillratio += -100 + 500 * skill_lv; + else + skillratio += -100 + 350 * skill_lv; + skillratio += ((sd) ? pc_checkskill(sd, CR_SPEARQUICKEN) * 50 : 0); + RE_LVL_DMOD(100); + break; + case LG_EARTHDRIVE: + skillratio += -100 + 380 * skill_lv + sstatus->str + sstatus->vit; // !TODO: What's the STR/VIT bonus? + + if( sc != nullptr && sc->getSCE( SC_SHIELD_POWER ) ){ + skillratio += skill_lv * 37 * pc_checkskill( sd, IG_SHIELD_MASTERY ); + } + + RE_LVL_DMOD(100); + break; + case LG_HESPERUSLIT: + if (sc && sc->getSCE(SC_INSPIRATION)) + skillratio += -100 + 450 * skill_lv; + else + skillratio += -100 + 300 * skill_lv; + skillratio += sstatus->vit / 6; // !TODO: What's the VIT bonus? + RE_LVL_DMOD(100); + break; + case SR_EARTHSHAKER: + if (tsc && ((tsc->option&(OPTION_HIDE|OPTION_CLOAK|OPTION_CHASEWALK)) || tsc->getSCE(SC_CAMOUFLAGE) || tsc->getSCE(SC_STEALTHFIELD) || tsc->getSCE(SC__SHADOWFORM))) { + //[(Skill Level x 300) x (Caster Base Level / 100) + (Caster STR x 3)] % + skillratio += -100 + 300 * skill_lv; + RE_LVL_DMOD(100); + skillratio += status_get_str(src) * 3; + } else { //[(Skill Level x 400) x (Caster Base Level / 100) + (Caster STR x 2)] % + skillratio += -100 + 400 * skill_lv; + RE_LVL_DMOD(100); + skillratio += status_get_str(src) * 2; + } + break; + + case SR_DRAGONCOMBO: + skillratio += 100 + 80 * skill_lv; + RE_LVL_DMOD(100); + break; + case SR_FALLENEMPIRE: + // ATK [(Skill Level x 300 + 100) x Caster Base Level / 150] % + skillratio += 300 * skill_lv; + RE_LVL_DMOD(150); + break; + case SR_TIGERCANNON: + { + unsigned int hp = sstatus->max_hp * (10 + (skill_lv * 2)) / 100, + sp = sstatus->max_sp * (5 + skill_lv) / 100; + + if (wd->miscflag&8) + // Base_Damage = [((Caster consumed HP + SP) / 2) x Caster Base Level / 100] % + skillratio += -100 + (hp + sp) / 2; + else + // Base_Damage = [((Caster consumed HP + SP) / 4) x Caster Base Level / 100] % + skillratio += -100 + (hp + sp) / 4; + RE_LVL_DMOD(100); + } + if (sc->getSCE(SC_GT_REVITALIZE)) + skillratio += skillratio * 30 / 100; + break; + case SR_SKYNETBLOW: + //ATK [{(Skill Level x 200) + (Caster AGI)} x Caster Base Level / 100] % + skillratio += -100 + 200 * skill_lv + sstatus->agi / 6; // !TODO: Confirm AGI bonus + RE_LVL_DMOD(100); + break; + + case SR_RAMPAGEBLASTER: + if (tsc && tsc->getSCE(SC_EARTHSHAKER)) { + skillratio += 1400 + 550 * skill_lv; + RE_LVL_DMOD(120); + } else { + skillratio += 900 + 350 * skill_lv; + RE_LVL_DMOD(150); + } + if (sc->getSCE(SC_GT_CHANGE)) + skillratio += skillratio * 30 / 100; + break; + case SR_KNUCKLEARROW: + if (wd->miscflag&4) { // ATK [(Skill Level x 150) + (1000 x Target current weight / Maximum weight) + (Target Base Level x 5) x (Caster Base Level / 150)] % + skillratio += -100 + 150 * skill_lv + status_get_lv(target) * 5; + if (tsd && tsd->weight) + skillratio += 100 * tsd->weight / tsd->max_weight; + RE_LVL_DMOD(150); + } else { + if (status_get_class_(target) == CLASS_BOSS) + skillratio += 400 + 200 * skill_lv; + else // ATK [(Skill Level x 100 + 500) x Caster Base Level / 100] % + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(100); + } + if (sc->getSCE(SC_GT_CHANGE)) + skillratio += skillratio * 30 / 100; + break; + case SR_WINDMILL: // ATK [(Caster Base Level + Caster DEX) x Caster Base Level / 100] % + skillratio += -100 + status_get_lv(src) + sstatus->dex; + RE_LVL_DMOD(100); + break; + case SR_GATEOFHELL: + if (sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SR_FALLENEMPIRE) + skillratio += -100 + 800 * skill_lv; + else + skillratio += -100 + 500 * skill_lv; + RE_LVL_DMOD(100); + if (sc->getSCE(SC_GT_REVITALIZE)) + skillratio += skillratio * 30 / 100; + break; + case SR_GENTLETOUCH_QUIET: + skillratio += -100 + 100 * skill_lv + sstatus->dex; + RE_LVL_DMOD(100); + break; + case SR_HOWLINGOFLION: + skillratio += -100 + 500 * skill_lv; + RE_LVL_DMOD(100); + break; + case SR_RIDEINLIGHTNING: + skillratio += -100 + 40 * skill_lv; + if (sd && sd->status.weapon == W_KNUCKLE) + skillratio += 50 * skill_lv; + RE_LVL_DMOD(100); + break; + case WM_SEVERE_RAINSTORM_MELEE: + //ATK [{(Caster DEX / 300 + AGI / 200)} x Caster Base Level / 100] % + skillratio += -100 + 100 * skill_lv + (sstatus->dex / 300 + sstatus->agi / 200); + if (wd->miscflag&4) // Whip/Instrument equipped + skillratio += 20 * skill_lv; + RE_LVL_DMOD(100); + break; + case WM_GREAT_ECHO: + skillratio += -100 + 250 + 500 * skill_lv; + if (sd) { + skillratio += pc_checkskill(sd, WM_LESSON) * 50; // !TODO: Confirm bonus + if (skill_check_pc_partner(sd, skill_id, &skill_lv, AREA_SIZE, 0) > 0) + skillratio *= 2; + } + RE_LVL_DMOD(100); + break; + case GN_CART_TORNADO: { // ATK [( Skill Level x 200 ) + ( Cart Weight / ( 150 - Caster Base STR ))] + ( Cart Remodeling Skill Level x 50 )] % + skillratio += -100 + 200 * skill_lv; + if(sd && sd->cart_weight) + skillratio += sd->cart_weight / 10 / (150 - min(sd->status.str,120)) + pc_checkskill(sd,GN_REMODELING_CART) * 50; + if (sc && sc->getSCE(SC_BIONIC_WOODENWARRIOR)) + skillratio *= 2; + } + break; + case GN_CARTCANNON: + skillratio += -100 + (250 + 20 * pc_checkskill(sd, GN_REMODELING_CART)) * skill_lv + 2 * sstatus->int_ / (6 - pc_checkskill(sd, GN_REMODELING_CART)); + RE_LVL_DMOD(100); + break; + case GN_SPORE_EXPLOSION: + skillratio += -100 + 400 + 200 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_BIONIC_WOODEN_FAIRY)) + skillratio *= 2; + break; + case GN_WALLOFTHORN: + skillratio += 10 * skill_lv; + break; + case GN_CRAZYWEED_ATK: + skillratio += -100 + 700 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case GN_SLINGITEM_RANGEMELEEATK: + if( sd ) { + switch( sd->itemid ) { + case ITEMID_APPLE_BOMB: + skillratio += 200 + status_get_str(src) + status_get_dex(src); + break; + case ITEMID_COCONUT_BOMB: + case ITEMID_PINEAPPLE_BOMB: + skillratio += 700 + status_get_str(src) + status_get_dex(src); + break; + case ITEMID_MELON_BOMB: + skillratio += 400 + status_get_str(src) + status_get_dex(src); + break; + case ITEMID_BANANA_BOMB: + skillratio += 777 + status_get_str(src) + status_get_dex(src); + break; + case ITEMID_BLACK_LUMP: + skillratio += -100 + (status_get_str(src) + status_get_agi(src) + status_get_dex(src)) / 3; + break; + case ITEMID_BLACK_HARD_LUMP: + skillratio += -100 + (status_get_str(src) + status_get_agi(src) + status_get_dex(src)) / 2; + break; + case ITEMID_VERY_HARD_LUMP: + skillratio += -100 + status_get_str(src) + status_get_agi(src) + status_get_dex(src); + break; + } + RE_LVL_DMOD(100); + } + break; + case GN_HELLS_PLANT_ATK: + skillratio += -100 + 100 * skill_lv + sstatus->int_ * (sd ? pc_checkskill(sd, AM_CANNIBALIZE) : 5); // !TODO: Confirm INT and Cannibalize bonus + RE_LVL_DMOD(100); + 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: + skillratio += -100 + 200 * skill_lv; + RE_LVL_DMOD(120); + if(tsc && tsc->getSCE(SC_JYUMONJIKIRI)) + skillratio += skill_lv * status_get_lv(src); + if (sc && sc->getSCE(SC_KAGEMUSYA)) + skillratio += skillratio * sc->getSCE(SC_KAGEMUSYA)->val2 / 100; + break; + case KO_HUUMARANKA: + skillratio += -100 + 150 * skill_lv + sstatus->str + (sd ? pc_checkskill(sd,NJ_HUUMA) * 100 : 0); + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_KAGEMUSYA)) + skillratio += skillratio * sc->getSCE(SC_KAGEMUSYA)->val2 / 100; + break; + case KO_SETSUDAN: + skillratio += 100 * (skill_lv - 1); + RE_LVL_DMOD(100); + if (tsc) { + struct status_change_entry *sce; + + if ((sce = tsc->getSCE(SC_SPIRIT)) || (sce = tsc->getSCE(SC_SOULGOLEM)) || (sce = tsc->getSCE(SC_SOULSHADOW)) || (sce = tsc->getSCE(SC_SOULFALCON)) || (sce = tsc->getSCE(SC_SOULFAIRY))) // Bonus damage added when target is soul linked. + skillratio += 200 * sce->val1; + } + break; + case KO_BAKURETSU: + skillratio += -100 + (sd ? pc_checkskill(sd,NJ_TOBIDOUGU) : 1) * (50 + sstatus->dex / 4) * skill_lv * 4 / 10; + RE_LVL_DMOD(120); + skillratio += 10 * (sd ? sd->status.job_level : 1); + if (sc && sc->getSCE(SC_KAGEMUSYA)) + skillratio += skillratio * sc->getSCE(SC_KAGEMUSYA)->val2 / 100; + break; + case KO_MAKIBISHI: + skillratio += -100 + 20 * skill_lv; + break; + case MH_NEEDLE_OF_PARALYZE: + skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->dex; // !TODO: Confirm Base Level and DEX bonus + break; + case MH_TOXIN_OF_MANDARA: + skillratio += -100 + 400 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->dex; // !TODO: Confirm Base Level and DEX bonus + break; + case MH_NEEDLE_STINGER: + skillratio += -100 + 200 + 500 * skill_lv * status_get_lv(src) / 100 + sstatus->dex; // !TODO: Confirm Base Level and DEX bonus + break; + case MH_STAHL_HORN: + skillratio += -100 + 1000 + 300 * skill_lv * status_get_lv(src) / 150 + sstatus->vit; // !TODO: Confirm VIT bonus + break; + case MH_GLANZEN_SPIES: + skillratio += -100 + 300 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->vit; // !TODO: Confirm VIT bonus + break; + case MH_LAVA_SLIDE: + skillratio += -100 + 50 * skill_lv; + break; + case MH_BLAST_FORGE: + skillratio += -100 + 70 * skill_lv * status_get_lv(src) / 100 + sstatus->str; + break; + case MH_SONIC_CRAW: + skillratio += -100 + 60 * skill_lv * status_get_lv(src) / 150; + break; + case MH_BLAZING_AND_FURIOUS: + skillratio += -100 + 80 * skill_lv * status_get_lv(src) / 100 + sstatus->str; + break; + case MH_THE_ONE_FIGHTER_RISES: + skillratio += -100 + 580 * skill_lv * status_get_lv(src) / 100 + sstatus->str; + break; + case MH_SILVERVEIN_RUSH: + skillratio += -100 + 250 * skill_lv * status_get_lv(src) / 100 + sstatus->str; // !TODO: Confirm STR bonus + break; + case MH_MIDNIGHT_FRENZY: + skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 150 + sstatus->str; // !TODO: Confirm STR bonus + break; + case MH_MAGMA_FLOW: + skillratio += -100 + (100 * skill_lv + 3 * status_get_lv(src)) * status_get_lv(src) / 120; + break; + case RL_MASS_SPIRAL: + skillratio += -100 + 200 * skill_lv; + break; + case RL_FIREDANCE: + skillratio += 100 + 100 * skill_lv; + skillratio += (sd ? pc_checkskill(sd, GS_DESPERADO) * 20 : 0); + RE_LVL_DMOD(100); + break; + case RL_BANISHING_BUSTER: + skillratio += -100 + 1000 + 200 * skill_lv; + RE_LVL_DMOD(100); + break; + case RL_S_STORM: + skillratio += -100 + 1700 + 200 * skill_lv; + break; + case RL_SLUGSHOT: + if (target->type == BL_MOB) + skillratio += -100 + 1200 * skill_lv; + else + skillratio += -100 + 2000 * skill_lv; + skillratio *= 2 + tstatus->size; + break; + case RL_D_TAIL: + skillratio += -100 + 500 + 200 * skill_lv; + if (sd && (wd->miscflag & 8)) + skillratio *= 2; + RE_LVL_DMOD(100); + break; + case RL_R_TRIP: + skillratio += -100 + 350 * skill_lv; + RE_LVL_DMOD(100); + break; + case RL_R_TRIP_PLUSATK: + skillratio += -100 + 300 + 300 * skill_lv; + break; + case RL_H_MINE: + if (sd && sd->flicker) // Flicker explosion damage: 500 + 300 * SkillLv + skillratio += -100 + 500 + 300 * skill_lv; + else // 200 + 200 * SkillLv + skillratio += -100 + 200 + 200 * skill_lv; + break; + case RL_HAMMER_OF_GOD: + skillratio += -100 + 100 * skill_lv; + if (sd) { + if (wd->miscflag & 8) + skillratio += 400 * sd->spiritball_old; + else + skillratio += 150 * sd->spiritball_old; + } + RE_LVL_DMOD(100); + break; + case RL_FIRE_RAIN: + case RL_AM_BLAST: + skillratio += -100 + 3500 + 300 * skill_lv; + break; + case SU_BITE: + skillratio += 100; + break; + case SU_SCRATCH: + skillratio += -50 + 50 * skill_lv; + break; + case SU_SCAROFTAROU: + skillratio += -100 + 100 * skill_lv; + if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE)) + skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src); + break; + case SU_PICKYPECK: + case SU_PICKYPECK_DOUBLE_ATK: + skillratio += 100 + 100 * skill_lv; + if (status_get_hp(target) < (status_get_max_hp(target) / 2)) + skillratio *= 2; + if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE)) + skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src); + break; + case SU_LUNATICCARROTBEAT: + case SU_LUNATICCARROTBEAT2: + skillratio += 100 + 100 * skill_lv; + if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE)) + skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src); + if (status_get_lv(src) > 99) + skillratio += sstatus->str; + RE_LVL_DMOD(100); + break; + case SU_SVG_SPIRIT: + skillratio += 150 + 150 * skill_lv; + if (sd && pc_checkskill(sd, SU_SPIRITOFLIFE)) + skillratio += skillratio * status_get_hp(src) / status_get_max_hp(src); + break; + case SJ_FULLMOONKICK: + skillratio += 1000 + 100 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_LIGHTOFMOON)) + skillratio += skillratio * sc->getSCE(SC_LIGHTOFMOON)->val2 / 100; + break; + case SJ_NEWMOONKICK: + skillratio += 600 + 100 * skill_lv; + break; + case SJ_STAREMPEROR: + skillratio += 700 + 200 * skill_lv; + break; + case SJ_SOLARBURST: + skillratio += 900 + 220 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_LIGHTOFSUN)) + skillratio += skillratio * sc->getSCE(SC_LIGHTOFSUN)->val2 / 100; + break; + case SJ_PROMINENCEKICK: + skillratio += 50 + 50 * skill_lv; + break; + case SJ_FALLINGSTAR_ATK: + case SJ_FALLINGSTAR_ATK2: + skillratio += 100 * skill_lv; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_LIGHTOFSTAR)) + skillratio += skillratio * sc->getSCE(SC_LIGHTOFSTAR)->val2 / 100; + break; + case DK_SERVANTWEAPON_ATK: + skillratio += -100 + 600 + 850 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case DK_SERVANT_W_PHANTOM: + skillratio += -100 + 200 + 300 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case DK_SERVANT_W_DEMOL: + skillratio += -100 + 500 * skill_lv; + RE_LVL_DMOD(100); + break; + case DK_HACKANDSLASHER: + case DK_HACKANDSLASHER_ATK: + skillratio += -100 + 350 + 820 * skill_lv; + skillratio += 7 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case DK_DRAGONIC_AURA: + skillratio += 3650 * skill_lv + 10 * sstatus->pow; + if (tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case DK_MADNESS_CRUSHER: + skillratio += -100 + 1000 + 3800 * skill_lv; + skillratio += 10 * sstatus->pow; + if( sd != nullptr ){ + int16 index = sd->equip_index[EQI_HAND_R]; + + if( index >= 0 && sd->inventory_data[index] != nullptr ){ + skillratio += sd->inventory_data[index]->weight / 10 * sd->inventory_data[index]->weapon_level; + } + } + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CHARGINGPIERCE_COUNT) && sc->getSCE(SC_CHARGINGPIERCE_COUNT)->val1 >= 10) + skillratio *= 2; + break; + case DK_STORMSLASH: + skillratio += -100 + 300 + 750 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_GIANTGROWTH) && rnd_chance(60, 100)) + skillratio *= 2; + break; + case DK_DRAGONIC_BREATH: + skillratio += -100 + 50 + 350 * skill_lv; + skillratio += 7 * sstatus->pow; + + if (sc && sc->getSCE(SC_DRAGONIC_AURA)) { + skillratio += 3 * sstatus->pow; + skillratio += (skill_lv * (sstatus->max_hp * 25 / 100) * 7) / 100; // Skill level x 0.07 x ((MaxHP / 4) + (MaxSP / 2)) + skillratio += (skill_lv * (sstatus->max_sp * 50 / 100) * 7) / 100; + } else { + skillratio += (skill_lv * (sstatus->max_hp * 25 / 100) * 5) / 100; // Skill level x 0.05 x ((MaxHP / 4) + (MaxSP / 2)) + skillratio += (skill_lv * (sstatus->max_sp * 50 / 100) * 5) / 100; + } + + RE_LVL_DMOD(100); + break; + case IQ_OLEUM_SANCTUM: + skillratio += -100 + 500 + 2000 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_MASSIVE_F_BLASTER: + skillratio += -100 + 2300 * skill_lv + 15 * sstatus->pow; + if (tstatus->race == RC_BRUTE || tstatus->race == RC_DEMON) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case IQ_EXPOSION_BLASTER: + skillratio += -100 + 450 + 2600 * skill_lv; + skillratio += 10 * sstatus->pow; + + if( tsc != nullptr && tsc->getSCE( SC_HOLY_OIL ) ){ + skillratio += 950 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case IQ_FIRST_BRAND: + skillratio += -100 + 1200 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_SECOND_FLAME: + skillratio += -100 + 200 + 2900 * skill_lv + 9 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_SECOND_FAITH: + skillratio += -100 + 100 + 2300 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_SECOND_JUDGEMENT: + skillratio += -100 + 150 + 2600 * skill_lv + 7 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_THIRD_PUNISH: + skillratio += -100 + 450 + 1800 * skill_lv; + skillratio += 10 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IQ_THIRD_FLAME_BOMB: + skillratio += -100 + 650 * skill_lv + 10 * sstatus->pow; + skillratio += sstatus->max_hp * 20 / 100; + RE_LVL_DMOD(100); + break; + case IQ_THIRD_CONSECRATION: + skillratio += -100 + 700 * skill_lv + 10 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case IG_GRAND_JUDGEMENT: + skillratio += -100 + 250 + 1500 * skill_lv + 10 * sstatus->pow; + if (tstatus->race == RC_PLANT || tstatus->race == RC_INSECT) + skillratio += 100 + 150 * skill_lv; + RE_LVL_DMOD(100); + if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0) + skillratio += skillratio * i / 100; + break; + case IG_SHIELD_SHOOTING: + skillratio += -100 + 1000 + 3500 * skill_lv; + skillratio += 10 * sstatus->pow; + skillratio += skill_lv * 150 * pc_checkskill( sd, IG_SHIELD_MASTERY ); + if (sd) { // Damage affected by the shield's weight and refine. Need official formula. [Rytech] + short index = sd->equip_index[EQI_HAND_L]; + + if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) { + skillratio += (sd->inventory_data[index]->weight * 7 / 6) / 10; + skillratio += sd->inventory.u.items_inventory[index].refine * 100; + } + } + RE_LVL_DMOD(100); + break; + case IG_OVERSLASH: + skillratio += -100 + 220 * skill_lv; + skillratio += pc_checkskill(sd, IG_SPEAR_SWORD_M) * 50 * skill_lv; + skillratio += 7 * sstatus->pow; + RE_LVL_DMOD(100); + if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0) + skillratio += skillratio * i / 100; + break; + case CD_EFFLIGO: + skillratio += -100 + 1650 * skill_lv + 7 * sstatus->pow; + skillratio += 8 * pc_checkskill( sd, CD_MACE_BOOK_M ); + if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON) { + skillratio += 150 * skill_lv; + skillratio += 7 * pc_checkskill( sd, CD_MACE_BOOK_M ); + } + RE_LVL_DMOD(100); + break; + case CD_PETITIO: + skillratio += -100 + (1050 + pc_checkskill(sd,CD_MACE_BOOK_M) * 50) * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case SHC_DANCING_KNIFE: + skillratio += -100 + 200 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case SHC_SAVAGE_IMPACT: + skillratio += -100 + 105 * skill_lv + 5 * sstatus->pow; + + if( sc != nullptr && sc->getSCE( SC_SHADOW_EXCEED ) ){ + skillratio += 20 * skill_lv + 3 * sstatus->pow; // !TODO: check POW ratio + } + + RE_LVL_DMOD(100); + break; + case SHC_ETERNAL_SLASH: + skillratio += -100 + 300 * skill_lv + 2 * sstatus->pow; + + if( sc != nullptr && sc->getSCE( SC_SHADOW_EXCEED ) ){ + skillratio += 120 * skill_lv + sstatus->pow; + } + + RE_LVL_DMOD(100); + break; + case SHC_SHADOW_STAB: + skillratio += -100 + 550 * skill_lv; + skillratio += 5 * sstatus->pow; + + if (wd->miscflag & SKILL_ALTDMG_FLAG) { + skillratio += 100 * skill_lv + 2 * sstatus->pow; + } + + RE_LVL_DMOD(100); + break; + case SHC_IMPACT_CRATER: + skillratio += -100 + 80 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case SHC_FATAL_SHADOW_CROW: + skillratio += -100 + 1300 * skill_lv + 10 * sstatus->pow; + if (tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_DRAGON) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case MT_AXE_STOMP: + skillratio += -100 + 450 + 1150 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case MT_MIGHTY_SMASH: + skillratio += -100 + 80 + 240 * skill_lv; + skillratio += 5 * sstatus->pow; + if (sc && sc->getSCE(SC_AXE_STOMP)) { + skillratio += 20; + skillratio += 5 * sstatus->pow; + } + RE_LVL_DMOD(100); + break; + case MT_RUSH_QUAKE: + skillratio += -100 + 3600 * skill_lv + 10 * sstatus->pow; + if (tstatus->race == RC_FORMLESS || tstatus->race == RC_INSECT) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case MT_A_MACHINE:// Formula unknown. Using Dancing Knife's formula for now. [Rytech] + skillratio += -100 + 200 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case MT_SPARK_BLASTER: + skillratio += -100 + 600 + 1400 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case MT_TRIPLE_LASER: + skillratio += -100 + 650 + 1150 * skill_lv; + skillratio += 12 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case ABC_ABYSS_DAGGER: + skillratio += -100 + 350 + 1400 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case ABC_UNLUCKY_RUSH: + skillratio += -100 + 100 + 300 * skill_lv + 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case ABC_CHAIN_REACTION_SHOT: + skillratio += -100 + 850 * skill_lv; + skillratio += 15 * sstatus->con; + RE_LVL_DMOD(100); + break; + case ABC_CHAIN_REACTION_SHOT_ATK: + skillratio += -100 + 800 + 2550 * skill_lv; + skillratio += 15 * sstatus->con; + RE_LVL_DMOD(100); + break; + case ABC_DEFT_STAB: + skillratio += -100 + 700 + 550 * skill_lv; + skillratio += 7 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case ABC_FRENZY_SHOT: + skillratio += -100 + 250 + 800 * skill_lv; + skillratio += 15 * sstatus->con; + RE_LVL_DMOD(100); + break; + case WH_HAWKRUSH: + skillratio += -100 + 500 * skill_lv + 5 * sstatus->con; + if (sd) + skillratio += skillratio * pc_checkskill(sd, WH_NATUREFRIENDLY) / 10; + RE_LVL_DMOD(100); + break; + case WH_HAWKBOOMERANG: + skillratio += -100 + 600 * skill_lv + 10 * sstatus->con; + if (sd) + skillratio += skillratio * pc_checkskill(sd, WH_NATUREFRIENDLY) / 10; + if (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH) + skillratio += skillratio * 50 / 100; + RE_LVL_DMOD(100); + break; + case WH_GALESTORM: + skillratio += -100 + 1350 * skill_lv; + skillratio += 10 * sstatus->con; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CALAMITYGALE) && (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH)) + skillratio += skillratio * 50 / 100; + break; + case WH_CRESCIVE_BOLT: + skillratio += -100 + 500 + 1300 * skill_lv; + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + if (sc) { + if (sc->getSCE(SC_CRESCIVEBOLT)) + skillratio += skillratio * (20 * sc->getSCE(SC_CRESCIVEBOLT)->val1) / 100; + + if (sc->getSCE(SC_CALAMITYGALE)) { + skillratio += skillratio * 20 / 100; + + if (tstatus->race == RC_BRUTE || tstatus->race == RC_FISH) + skillratio += skillratio * 50 / 100; + } + } + break; + case WH_DEEPBLINDTRAP: + case WH_SOLIDTRAP: + case WH_SWIFTTRAP: + case WH_FLAMETRAP: + skillratio += -100 + 850 * skill_lv + 5 * sstatus->con; + RE_LVL_DMOD(100); + skillratio += skillratio * (20 * (sd ? pc_checkskill(sd, WH_ADVANCED_TRAP) : 5)) / 100; + break; + case BO_ACIDIFIED_ZONE_WATER: + case BO_ACIDIFIED_ZONE_GROUND: + case BO_ACIDIFIED_ZONE_WIND: + case BO_ACIDIFIED_ZONE_FIRE: + case BO_ACIDIFIED_ZONE_WATER_ATK:// These deal the same damage? [Rytech] + case BO_ACIDIFIED_ZONE_GROUND_ATK: + case BO_ACIDIFIED_ZONE_WIND_ATK: + case BO_ACIDIFIED_ZONE_FIRE_ATK: + skillratio += -100 + 400 * skill_lv + 5 * sstatus->pow; + + if( sc != nullptr && sc->getSCE( SC_RESEARCHREPORT ) ){ + skillratio += skillratio * 50 / 100; + + if (tstatus->race == RC_FORMLESS || tstatus->race == RC_PLANT) + skillratio += skillratio * 50 / 100; + } + + RE_LVL_DMOD(100); + break; + case BO_EXPLOSIVE_POWDER: + skillratio += -100 + 500 + 650 * skill_lv; + skillratio += 5 * sstatus->pow; + if (sc && sc->getSCE(SC_RESEARCHREPORT)) + skillratio += 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case BO_MAYHEMIC_THORNS: + skillratio += -100 + 200 + 300 * skill_lv; + skillratio += 5 * sstatus->pow; + if (sc && sc->getSCE(SC_RESEARCHREPORT)) + skillratio += 150; + RE_LVL_DMOD(100); + break; + case TR_ROSEBLOSSOM: + skillratio += -100 + 200 + 2000 * skill_lv; + + if (sd && pc_checkskill(sd, TR_STAGE_MANNER) > 0) + skillratio += 3 * sstatus->con; + + if( tsc != nullptr && tsc->getSCE( SC_SOUNDBLEND ) ){ + skillratio += 200 * skill_lv; + } + + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_MYSTIC_SYMPHONY)) { + skillratio *= 2; + + if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN) + skillratio += skillratio * 50 / 100; + } + break; + case TR_ROSEBLOSSOM_ATK: + skillratio += -100 + 250 + 2800 * skill_lv; + + if (sd && pc_checkskill(sd, TR_STAGE_MANNER) > 0) + skillratio += 3 * sstatus->con; + + if( tsc != nullptr && tsc->getSCE( SC_SOUNDBLEND ) ){ + skillratio += 200 * skill_lv; + } + + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_MYSTIC_SYMPHONY)) { + skillratio *= 2; + + if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN) + skillratio += skillratio * 50 / 100; + } + break; + case TR_RHYTHMSHOOTING: + skillratio += -100 + 550 + 950 * skill_lv; + + if (sd && pc_checkskill(sd, TR_STAGE_MANNER) > 0) + skillratio += 5 * sstatus->con; + + if (tsc && tsc->getSCE(SC_SOUNDBLEND)) { + skillratio += 300 + 100 * skill_lv; + skillratio += 2 * sstatus->con; + } + + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_MYSTIC_SYMPHONY)) { + skillratio *= 2; + + if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN) + skillratio += skillratio * 50 / 100; + } + break; + case ABR_BATTLE_BUSTER:// Need official formula. + case ABR_DUAL_CANNON_FIRE:// Need official formula. + skillratio += -100 + 8000; + break; + case ABR_INFINITY_BUSTER:// Need official formula. + skillratio += -100 + 50000; + break; + case HN_SPIRAL_PIERCE_MAX: + skillratio += -100 + 1000 + 1500 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_TATICS) * 3 * skill_lv; + skillratio += 5 * sstatus->pow; + switch (status_get_size(target)){ + case SZ_SMALL: + skillratio = skillratio * 150 / 100; + break; + case SZ_MEDIUM: + skillratio = skillratio * 130 / 100; + break; + case SZ_BIG: + skillratio = skillratio * 120 / 100; + break; + } + RE_LVL_DMOD(100); + break; + case HN_SHIELD_CHAIN_RUSH: + skillratio += -100 + 850 + 1050 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_TATICS) * 3 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case HN_MEGA_SONIC_BLOW: + skillratio += -100 + 900 + 750 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_TATICS) * 5 * skill_lv; + skillratio += 5 * sstatus->pow; + if (status_get_hp(target) < status_get_max_hp(target) / 2) + skillratio *= 2; + RE_LVL_DMOD(100); + break; + case HN_DOUBLEBOWLINGBASH: + skillratio += -100 + 250 + 400 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_TATICS) * 3 * skill_lv; + skillratio += 5 * sstatus->pow; + RE_LVL_DMOD(100); + break; + case NW_HASTY_FIRE_IN_THE_HOLE: + skillratio += -100 + 1500 + 1500 * skill_lv; + skillratio += pc_checkskill( sd, NW_GRENADE_MASTERY ) * 20; + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + break; + case NW_BASIC_GRENADE: + skillratio += -100 + 1500 + 2100 * skill_lv; + skillratio += pc_checkskill( sd, NW_GRENADE_MASTERY ) * 50; + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + break; + case NW_GRENADES_DROPPING: + skillratio += -100 + 550 + 850 * skill_lv; + skillratio += pc_checkskill( sd, NW_GRENADE_MASTERY ) * 30; + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + break; + case NW_WILD_FIRE: + skillratio += -100 + 1500 + 3000 * skill_lv; + skillratio += 5 * sstatus->con; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 500 * skill_lv; + if (sd && sd->weapontype1 == W_SHOTGUN) + skillratio += 200 * skill_lv; + RE_LVL_DMOD(100); + break; + case NW_MAGAZINE_FOR_ONE: + skillratio += -100 + 250 + 500 * skill_lv; + skillratio += 5 * sstatus->con; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 100 * skill_lv; + if (sd && sd->weapontype1 == W_REVOLVER) + skillratio += 50 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case NW_SPIRAL_SHOOTING: + skillratio += -100 + 1200 + 1700 * skill_lv; + skillratio += 5 * sstatus->con; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 150 * skill_lv; + if (sd && sd->weapontype1 == W_RIFLE) + skillratio += 200 + 1100 * skill_lv; + RE_LVL_DMOD(100); + break; + case NW_ONLY_ONE_BULLET: + skillratio += -100 + 1200 + 3000 * skill_lv; + skillratio += 5 * sstatus->con; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 350 * skill_lv; + if (sd && sd->weapontype1 == W_REVOLVER) { + skillratio += 400 * skill_lv; + } + RE_LVL_DMOD(100); + break; + case NW_THE_VIGILANTE_AT_NIGHT: + if (sd && sd->weapontype1 == W_GATLING) { + skillratio += -100 + 300 * skill_lv; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 100 * skill_lv; + } else { + skillratio += -100 + 800 + 700 * skill_lv; + if (sc && sc->getSCE(SC_INTENSIVE_AIM_COUNT)) + skillratio += sc->getSCE(SC_INTENSIVE_AIM_COUNT)->val1 * 200 * skill_lv; + } + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + break; + case NW_MISSION_BOMBARD: + if( wd->miscflag&SKILL_ALTDMG_FLAG ){ + skillratio += -100 + 5000 + 1800 * skill_lv; + skillratio += pc_checkskill( sd, NW_GRENADE_MASTERY ) * 100; + }else{ + skillratio += -100 + 800 + 200 * skill_lv; + skillratio += pc_checkskill( sd, NW_GRENADE_MASTERY ) * 30; + } + skillratio += 5 * sstatus->con; + RE_LVL_DMOD(100); + break; + } + return skillratio; +} + +/*================================================================================================== + * Constant skill damage additions are added before SC modifiers and after skill base ATK calculation + *--------------------------------------------------------------------------------------------------* + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static int64 battle_calc_skill_constant_addition(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + map_session_data *tsd = BL_CAST(BL_PC, target); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + int64 atk = 0; + + //Constant/misc additions from skills + switch (skill_id) { + case MO_EXTREMITYFIST: + atk = 250 + 150 * skill_lv; + break; + case PA_SHIELDCHAIN: + if (sd) { + short index = sd->equip_index[EQI_HAND_L]; + // Bonus damage: [max(100, Random(100, 0.7*weight + pow(skill level + refine)))] + if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) { + // First calculate the random part of the bonus + int bonus = (7 * sd->inventory_data[index]->weight) / 100; + bonus += static_cast(pow(skill_lv + sd->inventory.u.items_inventory[index].refine, 2)); + // Now get a random value between 100 and the random part + atk = max(100, rnd_value(100, bonus)); + } + } + break; +#ifndef RENEWAL + case GS_MAGICALBULLET: + if (sstatus->matk_max > sstatus->matk_min) + atk = sstatus->matk_min + rnd()%(sstatus->matk_max - sstatus->matk_min); + else + atk = sstatus->matk_min; + break; +#endif +#ifdef RENEWAL + case HT_FREEZINGTRAP: + if(sd) + atk = 40 * pc_checkskill(sd, RA_RESEARCHTRAP); + break; +#endif + } + return atk; +} + +/*============================================================== + * Stackable SC bonuses added on top of calculated skill damage + *-------------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_attack_sc_bonus(struct Damage* wd, struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + uint8 anger_id = 0; // SLS Anger + + // Kagerou/Oboro Earth Charm effect +15% wATK + if(sd && sd->spiritcharm_type == CHARM_TYPE_LAND && sd->spiritcharm > 0) { + ATK_ADDRATE(wd->damage, wd->damage2, 15 * sd->spiritcharm); +#ifdef RENEWAL + ATK_ADDRATE(wd->weaponAtk, wd->weaponAtk2, 15 * sd->spiritcharm); +#endif + } + + //The following are applied on top of current damage and are stackable. + if (sc) { +#ifdef RENEWAL + if (sc->getSCE(SC_WATK_ELEMENT) && skill_id != ASC_METEORASSAULT) + ATK_ADDRATE(wd->weaponAtk, wd->weaponAtk2, sc->getSCE(SC_WATK_ELEMENT)->val2); + if (sc->getSCE(SC_DRUMBATTLE)) + ATK_ADD(wd->equipAtk, wd->equipAtk2, sc->getSCE(SC_DRUMBATTLE)->val2); + if (sc->getSCE(SC_MADNESSCANCEL)) + ATK_ADD(wd->equipAtk, wd->equipAtk2, 100); + if (sc->getSCE(SC_MAGICALBULLET)) { + short tmdef = tstatus->mdef + tstatus->mdef2; + + if (sstatus->matk_min > tmdef && sstatus->matk_max > sstatus->matk_min) { + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, i64max((sstatus->matk_min + rnd() % (sstatus->matk_max - sstatus->matk_min)) - tmdef, 0)); + } else { + ATK_ADD(wd->weaponAtk, wd->weaponAtk2, i64max(sstatus->matk_min - tmdef, 0)); + } + } + if (sc->getSCE(SC_GATLINGFEVER)) + ATK_ADD(wd->equipAtk, wd->equipAtk2, sc->getSCE(SC_GATLINGFEVER)->val3); +#else + //ATK percent modifier (in pre-renewal, it's applied multiplicatively after the skill ratio) + ATK_RATE(wd->damage, wd->damage2, battle_get_atkpercent(*src, skill_id, *sc)); + ATK_RATER(wd->basedamage, battle_get_atkpercent(*src, 0, *sc)); +#endif + if (sc->getSCE(SC_SPIRIT)) { + if (skill_id == AS_SONICBLOW && sc->getSCE(SC_SPIRIT)->val2 == SL_ASSASIN) { + ATK_ADDRATE(wd->damage, wd->damage2, map_flag_gvg2(src->m) ? 25 : 100); //+25% dmg on woe/+100% dmg on nonwoe + RE_ALLATK_ADDRATE(wd, map_flag_gvg2(src->m) ? 25 : 100); //+25% dmg on woe/+100% dmg on nonwoe + } else if (skill_id == CR_SHIELDBOOMERANG && sc->getSCE(SC_SPIRIT)->val2 == SL_CRUSADER) { + ATK_ADDRATE(wd->damage, wd->damage2, 100); + RE_ALLATK_ADDRATE(wd, 100); + } + } + if (sc->getSCE(SC_GT_CHANGE)) + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_GT_CHANGE)->val1); +#ifdef RENEWAL + if (sc->getSCE(SC_EDP)) { + switch(skill_id) { + // Renewal: Venom Splasher, Meteor Assault, Grimtooth and Venom Knife ignore EDP + case TF_SPRINKLESAND: + case AS_SPLASHER: + case ASC_METEORASSAULT: + case AS_GRIMTOOTH: + case AS_VENOMKNIFE: + break; // skills above have no effect with EDP + + default: // fall through to apply EDP bonuses + // Renewal EDP formula [helvetica] + // weapon atk * (2.5 + (edp level * .3)) + // equip atk * (2.5 + (edp level * .3)) + ATK_RATE(wd->weaponAtk, wd->weaponAtk2, 250 + (sc->getSCE(SC_EDP)->val1 * 30)); + ATK_RATE(wd->equipAtk, wd->equipAtk2, 250 + (sc->getSCE(SC_EDP)->val1 * 30)); + break; + } + } +#endif + if (sc->getSCE(SC_DANCEWITHWUG)) { + if (skill_get_inf2(skill_id, INF2_INCREASEDANCEWITHWUGDAMAGE)) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_DANCEWITHWUG)->val1 * 10 * battle_calc_chorusbonus(sd)); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_DANCEWITHWUG)->val1 * 10 * battle_calc_chorusbonus(sd)); + } + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_DANCEWITHWUG)->val1 * 2 * battle_calc_chorusbonus(sd)); +#ifdef RENEWAL + ATK_ADDRATE(wd->equipAtk, wd->equipAtk2, sc->getSCE(SC_DANCEWITHWUG)->val1 * 2 * battle_calc_chorusbonus(sd)); +#endif + } + if(sc->getSCE(SC_ZENKAI) && sstatus->rhw.ele == sc->getSCE(SC_ZENKAI)->val2) { + ATK_ADD(wd->damage, wd->damage2, 200); +#ifdef RENEWAL + ATK_ADD(wd->equipAtk, wd->equipAtk2, 200); +#endif + } + if (sc->getSCE(SC_EQC)) { + ATK_ADDRATE(wd->damage, wd->damage2, -sc->getSCE(SC_EQC)->val2); +#ifdef RENEWAL + ATK_ADDRATE(wd->equipAtk, wd->equipAtk2, -sc->getSCE(SC_EQC)->val2); +#endif + } + if(sc->getSCE(SC_STYLE_CHANGE)) { + TBL_HOM *hd = BL_CAST(BL_HOM,src); + + if(hd) { + ATK_ADD(wd->damage, wd->damage2, hd->homunculus.spiritball * 3); + RE_ALLATK_ADD(wd, hd->homunculus.spiritball * 3); + } + } + if(sc->getSCE(SC_UNLIMIT) && (wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) { + switch(skill_id) { + case RA_WUGDASH: + case RA_WUGSTRIKE: + case RA_WUGBITE: + break; + default: + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_UNLIMIT)->val2); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_UNLIMIT)->val2); + break; + } + } + if (sc->getSCE(SC_HEAT_BARREL)) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_HEAT_BARREL)->val3); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_HEAT_BARREL)->val3); + } + if((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) { + if (sc->getSCE(SC_MTF_RANGEATK)) { // Monster Transformation bonus + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_MTF_RANGEATK)->val1); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_MTF_RANGEATK)->val1); + } + if (sc->getSCE(SC_MTF_RANGEATK2)) { // Monster Transformation bonus + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_MTF_RANGEATK2)->val1); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_MTF_RANGEATK2)->val1); + } + if (sc->getSCE(SC_ARCLOUSEDASH) && sc->getSCE(SC_ARCLOUSEDASH)->val4) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_ARCLOUSEDASH)->val4); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_ARCLOUSEDASH)->val4); + } + } + + if (sd && wd->flag&BF_WEAPON && sc->getSCE(SC_GVG_GIANT) && sc->getSCE(SC_GVG_GIANT)->val3) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_GVG_GIANT)->val3); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_GVG_GIANT)->val3); + } + + if (skill_id == 0 && sc->getSCE(SC_EXEEDBREAK)) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_EXEEDBREAK)->val2); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_EXEEDBREAK)->val2); + } + if (sc->getSCE(SC_PYREXIA) && sc->getSCE(SC_PYREXIA)->val3 == 0 && skill_id == 0) { + ATK_ADDRATE(wd->damage, wd->damage2, sc->getSCE(SC_PYREXIA)->val2); + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_PYREXIA)->val2); + } + + if (sc->getSCE(SC_MIRACLE)) + anger_id = 2; // Always treat all monsters as star flagged monster when in miracle state + if (sc->getSCE(SC_HIDDEN_CARD) && (wd->flag&BF_LONG) == BF_LONG) + RE_ALLATK_ADDRATE(wd, sc->getSCE(SC_HIDDEN_CARD)->val3); + } + + if ((wd->flag&(BF_LONG|BF_MAGIC)) == BF_LONG) { + if (sd && pc_checkskill(sd, SU_POWEROFLIFE) > 0 && pc_checkskill_summoner(sd, SUMMONER_POWER_LIFE) >= 20) { + ATK_ADDRATE(wd->damage, wd->damage2, 20); + RE_ALLATK_ADDRATE(wd, 20); + } + } + + if (sd != nullptr && !anger_id) + ARR_FIND(0, MAX_PC_FEELHATE, anger_id, status_get_class(target) == sd->hate_mob[anger_id]); + + uint16 anger_level; + if (sd != nullptr && anger_id < MAX_PC_FEELHATE && (anger_level = pc_checkskill(sd, sg_info[anger_id].anger_id))) { + int skillratio = sd->status.base_level + sstatus->dex + sstatus->luk; + + if (anger_id == 2) + skillratio += sstatus->str; // SG_STAR_ANGER additionally has STR added in its formula. + if (anger_level < 4) + skillratio /= 12 - 3 * anger_level; + ATK_ADDRATE(wd->damage, wd->damage2, skillratio); +#ifdef RENEWAL + RE_ALLATK_ADDRATE(wd, skillratio); +#endif + } +} + +/*==================================== + * Calc defense damage reduction + *------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_defense_reduction(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + map_session_data *tsd = BL_CAST(BL_PC, target); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + + //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; + + if (sd) { + int i = sd->indexed_bonus.ignore_def_by_race[tstatus->race] + sd->indexed_bonus.ignore_def_by_race[RC_ALL]; + i += sd->indexed_bonus.ignore_def_by_class[tstatus->class_] + sd->indexed_bonus.ignore_def_by_class[CLASS_ALL]; + if (i) { + i = min(i,100); //cap it to 100 for 0 def min + def1 -= def1 * i / 100; + def2 -= def2 * i / 100; + } + + //Kagerou/Oboro Earth Charm effect +10% eDEF + if(sd->spiritcharm_type == CHARM_TYPE_LAND && sd->spiritcharm > 0) { + short si = 10 * sd->spiritcharm; + def1 = (def1 * (100 + si)) / 100; + } + } + + if (sc && sc->getSCE(SC_EXPIATIO)) { + short i = 5 * sc->getSCE(SC_EXPIATIO)->val1; // 5% per level + + i = min(i,100); //cap it to 100 for 0 def min + def1 = (def1*(100-i))/100; + def2 = (def2*(100-i))/100; + } + + if (tsc) { + if (tsc->getSCE(SC_FORCEOFVANGUARD)) { + short i = 2 * tsc->getSCE(SC_FORCEOFVANGUARD)->val1; + + def1 = (def1 * (100 + i)) / 100; + } + + if( tsc->getSCE(SC_CAMOUFLAGE) ){ + short i = 5 * tsc->getSCE(SC_CAMOUFLAGE)->val3; //5% per second + + i = min(i,100); //cap it to 100 for 0 def min + def1 = (def1*(100-i))/100; + def2 = (def2*(100-i))/100; + } + + if (tsc->getSCE(SC_GT_REVITALIZE)) + def1 += tsc->getSCE(SC_GT_REVITALIZE)->val4; + + if (tsc->getSCE(SC_OVERED_BOOST) && target->type == BL_PC) + def1 = (def1 * tsc->getSCE(SC_OVERED_BOOST)->val4) / 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 + + //Official servers limit the count to 22 targets + target_count = min(unit_counttargeted(target), (100 / battle_config.vit_penalty_num) + (battle_config.vit_penalty_count - 1)); + if(target_count >= battle_config.vit_penalty_count) { + if(battle_config.vit_penalty_type == 1) { + if( !tsc || !tsc->getSCE(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->getSCE(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(def2 < 1) + def2 = 1; + } + +#ifdef RENEWAL + if (skill_id == AM_ACIDTERROR) + def2 = 0; // Ignore only status defense. +#endif + + //Damage reduction based on vitality + if (tsd) { //Sd vit-eq + int skill; +#ifndef RENEWAL + //Damage reduction: [VIT*0.3] + RND(0, [VIT^2/150] - [VIT*0.3] - 1) + [VIT*0.5] + vit_def = ((3 * def2) / 10); + vit_def += rnd_value(0, max(0, (def2 * def2) / 150 - ((3 * def2) / 10) - 1)); + vit_def += (def2 / 2); +#else + vit_def = def2; +#endif + if (src->type == BL_MOB && (battle_check_undead(sstatus->race, sstatus->def_ele) || sstatus->race == RC_DEMON) && //This bonus already doesn't work vs players + (skill = pc_checkskill(tsd, AL_DP)) > 0) + vit_def += (int)(((float)tsd->status.base_level / 25.0 + 3.0) * skill + 0.5); + if( src->type == BL_MOB && (skill=pc_checkskill(tsd,RA_RANGERMAIN))>0 && + (sstatus->race == RC_BRUTE || sstatus->race == RC_PLAYER_DORAM || sstatus->race == RC_FISH || sstatus->race == RC_PLANT) ) + vit_def += skill*5; + if( src->type == BL_MOB && (skill = pc_checkskill(tsd, NC_RESEARCHFE)) > 0 && + (sstatus->def_ele == ELE_FIRE || sstatus->def_ele == ELE_EARTH) ) + vit_def += skill * 10; + } else { //Mob-Pet vit-eq +#ifndef RENEWAL + //VIT + rnd(0,[VIT/20]^2-1) + vit_def = (def2/20)*(def2/20); + if (tsc && tsc->getSCE(SC_SKA)) + vit_def += 100; //Eska increases the random part of the formula by 100 + vit_def = def2 + (vit_def>0?rnd()%vit_def:0); +#else + //SoftDEF of monsters is floor((BaseLevel+Vit)/2) + vit_def = def2; +#endif + } + + if (battle_config.weapon_defense_type) { + vit_def += def1*battle_config.weapon_defense_type; + def1 = 0; + } + +#ifdef RENEWAL + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd->miscflag); + + if (nk[NK_SIMPLEDEFENSE]) { + // Defense reduction by flat value. + // This completely bypasses the normal RE DEF Reduction formula. + wd->damage -= (def1 + vit_def); + if (is_attack_left_handed(src, skill_id)) + wd->damage2 -= (def1 + vit_def); + } + else { + /** + * RE DEF Reduction + * Damage = Attack * (4000+eDEF)/(4000+eDEF*10) - sDEF + * Pierce defence gains 1 atk per def/2 + */ + if (def1 == -400) /* -400 creates a division by 0 and subsequently crashes */ + def1 = -399; + ATK_ADD2(wd->damage, wd->damage2, + is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ? (def1 * battle_calc_attack_skill_ratio(wd, src, target, skill_id, skill_lv)) / 200 : 0, + is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ? (def1 * battle_calc_attack_skill_ratio(wd, src, target, skill_id, skill_lv)) / 200 : 0 + ); + if (!attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) && !is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R)) + wd->damage = wd->damage * (4000 + def1) / (4000 + 10 * def1) - vit_def; + if (is_attack_left_handed(src, skill_id) && !attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) && !is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L)) + wd->damage2 = wd->damage2 * (4000 + def1) / (4000 + 10 * def1) - vit_def; + } +#else + if (def1 > 100) def1 = 100; + ATK_RATE2(wd->damage, wd->damage2, + attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ?100:(is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ? (int64)is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R)*(def1+vit_def) : (100-def1)), + attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ?100:(is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ? (int64)is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L)*(def1+vit_def) : (100-def1)) + ); + ATK_RATER(wd->basedamage, 100 - def1); + ATK_ADD2(wd->damage, wd->damage2, + attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_R) || is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_R) ?0:-vit_def, + attack_ignores_def(wd, src, target, skill_id, skill_lv, EQI_HAND_L) || is_attack_piercing(wd, src, target, skill_id, skill_lv, EQI_HAND_L) ?0:-vit_def + ); + wd->basedamage -= vit_def; +#endif +} + +/*==================================== + * Modifiers ignoring DEF + *------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_attack_post_defense(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + status_change *sc = status_get_sc(src); + status_data* sstatus = status_get_status_data(*src); + + // Post skill/vit reduction damage increases +#ifndef RENEWAL + //Refine bonus + if (sd) { + if (battle_skill_stacks_masteries_vvs(skill_id, BCHK_REFINE)) { + ATK_ADD2(wd->damage, wd->damage2, sstatus->rhw.atk2, sstatus->lhw.atk2); + } + wd->basedamage += sstatus->rhw.atk2; + } + + //After DEF reduction, damage can be negative, refine bonus works against that value + //After refinement bonus was applied, damage is capped to 1, then masteries are applied + battle_min_damage(*wd, *src, skill_id, 1); + + battle_calc_attack_masteries(wd, src, target, skill_id, skill_lv); +#endif + if (sc) { // SC skill damages + if (sc->getSCE(SC_AURABLADE) +#ifndef RENEWAL + && skill_id != LK_SPIRALPIERCE && skill_id != ML_SPIRALPIERCE +#endif + ) { +#ifdef RENEWAL + ATK_ADD(wd->damage, wd->damage2, (3 + sc->getSCE(SC_AURABLADE)->val1) * status_get_lv(src)); // !TODO: Confirm formula +#else + ATK_ADD(wd->damage, wd->damage2, 20 * sc->getSCE(SC_AURABLADE)->val1); +#endif + } + } + + //Set to min of 1 + battle_min_damage(*wd, *src, skill_id, 1); + +#ifdef RENEWAL + switch (skill_id) { + case AS_SONICBLOW: + if(sd && pc_checkskill(sd,AS_SONICACCEL)>0) + ATK_ADDRATE(wd->damage, wd->damage2, 90); + break; + } +#endif +} + +/*================================================================================= + * "Plant"-type (mobs that only take 1 damage from all sources) damage calculation + *--------------------------------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_attack_plant(struct Damage* wd, struct block_list *src,struct block_list *target, uint16 skill_id, uint16 skill_lv) +{ + status_data* tstatus = status_get_status_data(*target); + bool attack_hits = is_attack_hitting(wd, src, target, skill_id, skill_lv, false); + + if (skill_id != SN_SHARPSHOOTING && skill_id != RA_ARROWSTORM) + status_change_end(src, SC_CAMOUFLAGE); + + //Plants receive 1 damage when hit + if( attack_hits || wd->damage > 0 ) + wd->damage = 1; //In some cases, right hand no need to have a weapon to deal a damage + if( is_attack_left_handed(src, skill_id) && (attack_hits || wd->damage2 > 0) ) { + map_session_data *sd = BL_CAST(BL_PC, src); + + if (sd && sd->status.weapon == W_KATAR) + wd->damage2 = 0; //No backhand damage against plants + else + wd->damage2 = 1; //Deal 1 HP damage as long as there is a weapon in the left hand + } + + if (attack_hits && target->type == BL_MOB) { + status_change *sc = status_get_sc(target); + int64 damage_dummy = 1; + + if (sc && !battle_status_block_damage(src, target, sc, wd, damage_dummy, skill_id, skill_lv)) { // Statuses that reduce damage to 0. + wd->damage = wd->damage2 = 0; + return; + } + } + + if( attack_hits && status_get_class(target) == MOBID_EMPERIUM ) { + if(target && !battle_can_hit_gvg_target(src,target,skill_id,(skill_id)?BF_SKILL:0) && map_flag_gvg2(target->m)) { + wd->damage = wd->damage2 = 0; + return; + } + + const int right_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_R, false); + const int left_element = battle_get_weapon_element(wd, src, target, skill_id, skill_lv, EQI_HAND_L, false); + + if (wd->damage > 0) { + wd->damage = battle_attr_fix(src, target, wd->damage, right_element, tstatus->def_ele, tstatus->ele_lv); + wd->damage = battle_calc_gvg_damage(src, target, wd->damage, skill_id, wd->flag); + } else if (wd->damage2 > 0) { + wd->damage2 = battle_attr_fix(src, target, wd->damage2, left_element, tstatus->def_ele, tstatus->ele_lv); + wd->damage2 = battle_calc_gvg_damage(src, target, wd->damage2, skill_id, wd->flag); + } + return; + } + + // Triple Attack has a special property that it does not split damage on plant mode + // In pre-renewal, it requires the monster to have exactly 100 def + if (skill_id == MO_TRIPLEATTACK && wd->div_ < 0 +#ifndef RENEWAL + && tstatus->def == 100 +#endif + ) + wd->div_ *= -1; + + //For plants we don't continue with the weapon attack code, so we have to apply DAMAGE_DIV_FIX here + battle_apply_div_fix(wd, skill_id); + + //If there is left hand damage, total damage can never exceed 2, even on multiple hits + if(wd->damage > 1 && wd->damage2 > 0) { + wd->damage = 1; + wd->damage2 = 1; + } +} + +/*======================================================================================== + * Perform left/right hand weapon damage calculation based on previously calculated damage + *---------------------------------------------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_attack_left_right_hands(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + + if (sd) { + int skill; + + 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; +#ifdef RENEWAL + } else if(is_attack_left_handed(src, skill_id) && sd->status.weapon != W_KATAR) { //Dual-wield +#else + } else if(is_attack_left_handed(src, skill_id)) { //Dual-wield +#endif + // If you only have a weapon in the left hand, then your main hand damage will be identical to an unarmed attack + if (is_attack_right_handed(src, skill_id) && wd->damage) { + if( (sd->class_&MAPID_BASEMASK) == MAPID_THIEF ) { + skill = pc_checkskill(sd,AS_RIGHT); + ATK_RATER(wd->damage, 50 + (skill * 10)) + } + else if(sd->class_ == MAPID_KAGEROUOBORO) { + skill = pc_checkskill(sd,KO_RIGHT); + ATK_RATER(wd->damage, 70 + (skill * 10)) + } + if(wd->damage < 1) + wd->damage = 1; + } + // Left hand damage will always be adjusted, even if you don't have a weapon in the main hand + if (wd->damage2) { + if( (sd->class_&MAPID_BASEMASK) == MAPID_THIEF) { + skill = pc_checkskill(sd,AS_LEFT); + ATK_RATEL(wd->damage2, 30 + (skill * 10)) + } + else if(sd->class_ == MAPID_KAGEROUOBORO) { + skill = pc_checkskill(sd,KO_LEFT); + ATK_RATEL(wd->damage2, 50 + (skill * 10)) + } + if(wd->damage2 < 1) + wd->damage2 = 1; + } + } + } + + if(!is_attack_right_handed(src, skill_id) && !is_attack_left_handed(src, skill_id) && wd->damage) + wd->damage=0; + + if(!is_attack_left_handed(src, skill_id) && wd->damage2) + wd->damage2=0; +} + +/** +* Check if bl is devoted by someone +* @param bl +* @return 'd_bl' if devoted or nullptr if not devoted +*/ +struct block_list *battle_check_devotion(struct block_list *bl) { + struct block_list *d_bl = nullptr; + + if (battle_config.devotion_rdamage && battle_config.devotion_rdamage > rnd() % 100) { + status_change *sc = status_get_sc(bl); + if (sc && sc->getSCE(SC_DEVOTION)) + d_bl = map_id2bl(sc->getSCE(SC_DEVOTION)->val1); + } + return d_bl; +} + +/*========================================== + * BG/GvG attack modifiers + *------------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_attack_gvg_bg(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + if( wd->damage + wd->damage2 ) { //There is a total damage value + if( src != target && //Don't reflect your own damage (Grand Cross) + (!skill_id || skill_id || + (src->type == BL_SKILL && (skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM))) ) { + int64 damage = wd->damage + wd->damage2, rdamage = 0; + map_session_data *tsd = BL_CAST(BL_PC, target); + status_data* sstatus = status_get_status_data(*src); + t_tick tick = gettick(), rdelay = 0; + + rdamage = battle_calc_return_damage(target, src, &damage, wd->flag, skill_id, false); + if( rdamage > 0 ) { //Item reflect gets calculated before any mapflag reducing is applicated + struct block_list *d_bl = battle_check_devotion(src); + + rdelay = clif_damage(*src, (d_bl == nullptr) ? *src : *d_bl, tick, wd->amotion, sstatus->dmotion, rdamage, 1, DMG_ENDURE, 0, false); + if( tsd ) + battle_drain(tsd, src, rdamage, rdamage, sstatus->race, sstatus->class_); + //Use Reflect Shield to signal this kind of skill trigger [Skotlex] + battle_delay_damage(tick, wd->amotion, target, (!d_bl) ? src : d_bl, 0, CR_REFLECTSHIELD, 0, rdamage, ATK_DEF, rdelay, true, false); + skill_additional_effect(target, (!d_bl) ? src : d_bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL, ATK_DEF, tick); + } + } + + struct map_data *mapdata = map_getmapdata(target->m); + + if(!wd->damage2) { + wd->damage = battle_calc_damage(src,target,wd,wd->damage,skill_id,skill_lv); + if( mapdata_flag_gvg2(mapdata) ) + wd->damage=battle_calc_gvg_damage(src,target,wd->damage,skill_id,wd->flag); + else if( mapdata->getMapFlag(MF_BATTLEGROUND) ) + wd->damage=battle_calc_bg_damage(src,target,wd->damage,skill_id,wd->flag); + } + else if(!wd->damage) { + wd->damage2 = battle_calc_damage(src,target,wd,wd->damage2,skill_id,skill_lv); + if( mapdata_flag_gvg2(mapdata) ) + wd->damage2 = battle_calc_gvg_damage(src,target,wd->damage2,skill_id,wd->flag); + else if( mapdata->getMapFlag(MF_BATTLEGROUND) ) + wd->damage2 = battle_calc_bg_damage(src,target,wd->damage2,skill_id,wd->flag); + } + else { + wd->damage = battle_calc_damage(src, target, wd, wd->damage, skill_id, skill_lv); + wd->damage2 = battle_calc_damage(src, target, wd, wd->damage2, skill_id, skill_lv); + if (mapdata_flag_gvg2(mapdata)) { + wd->damage = battle_calc_gvg_damage(src, target, wd->damage, skill_id, wd->flag); + wd->damage2 = battle_calc_gvg_damage(src, target, wd->damage2, skill_id, wd->flag); + } + else if (mapdata->getMapFlag(MF_BATTLEGROUND)) { + wd->damage = battle_calc_bg_damage(src, target, wd->damage, skill_id, wd->flag); + wd->damage2 = battle_calc_bg_damage(src, target, wd->damage2, skill_id, wd->flag); + } + if(wd->damage > 1 && wd->damage2 < 1) wd->damage2 = 1; + } + } +} + +/*========================================== + * final ATK modifiers - after BG/GvG calc + *------------------------------------------ + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static void battle_calc_weapon_final_atk_modifiers(struct Damage* wd, struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv) +{ + map_session_data *sd = BL_CAST(BL_PC, src); + map_session_data *tsd = BL_CAST(BL_PC, target); + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + int skill_damage = 0; + + //Reject Sword bugreport:4493 by Daegaladh + if(wd->damage && tsc && tsc->getSCE(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->getSCE(SC_REJECTSWORD)->val2 + ) + { + ATK_RATER(wd->damage, 50) + clif_skill_nodamage(target,target,ST_REJECTSWORD, tsc->getSCE(SC_REJECTSWORD)->val1,1); + battle_fix_damage(target,src,wd->damage,clif_damage(*target,*src,gettick(),0,0,wd->damage,0,DMG_NORMAL,0,false),ST_REJECTSWORD); + if (status_isdead(*target)) + return; + if( --(tsc->getSCE(SC_REJECTSWORD)->val3) <= 0 ) + status_change_end(target, SC_REJECTSWORD); + } + + if( tsc && tsc->getSCE(SC_CRESCENTELBOW) && wd->flag&BF_SHORT && rnd()%100 < tsc->getSCE(SC_CRESCENTELBOW)->val2 ) { + //ATK [{(Target HP / 100) x Skill Level} x Caster Base Level / 125] % + [Received damage x {1 + (Skill Level x 0.2)}] + int64 rdamage = 0; + int ratio = (int64)(status_get_hp(src) / 100) * tsc->getSCE(SC_CRESCENTELBOW)->val1 * status_get_lv(target) / 125; + if (ratio > 5000) ratio = 5000; // Maximum of 5000% ATK + rdamage = battle_calc_base_damage(target,tstatus,&tstatus->rhw,tsc,sstatus->size,0); + rdamage = (int64)rdamage * ratio / 100 + wd->damage * (10 + tsc->getSCE(SC_CRESCENTELBOW)->val1 * 20 / 10) / 10; + skill_blown(target, src, skill_get_blewcount(SR_CRESCENTELBOW_AUTOSPELL, tsc->getSCE(SC_CRESCENTELBOW)->val1), unit_getdir(src), BLOWN_NONE); + clif_skill_damage(target, src, gettick(), status_get_amotion(src), 0, rdamage, + 1, SR_CRESCENTELBOW_AUTOSPELL, tsc->getSCE(SC_CRESCENTELBOW)->val1, DMG_SINGLE); // This is how official does + clif_damage(*src, *target, gettick(), status_get_amotion(src)+1000, 0, rdamage/10, 1, DMG_NORMAL, 0, false); + battle_fix_damage(target, src, rdamage, 0, SR_CRESCENTELBOW); + status_damage(src, target, rdamage/10, 0, 0, 1, 0); + status_change_end(target, SC_CRESCENTELBOW); + } + + if( sc ) { + //SC_FUSION hp penalty [Komurka] + if (sc->getSCE(SC_FUSION)) { + unsigned int hp = sstatus->max_hp; + + if (sd && tsd) { + hp = hp / 13; + 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); + } + if (sc->getSCE(SC_VIGOR)) + status_zap(src, sc->getSCE(SC_VIGOR)->val2, 0); + // Only affecting non-skills + if (!skill_id && wd->dmg_lv > ATK_BLOCK) { + if (sc->getSCE(SC_ENCHANTBLADE)) { + //[((Skill Lv x 20) + 100) x (casterBaseLevel / 150)] + casterInt + MATK - MDEF - MDEF2 + int64 enchant_dmg = sc->getSCE(SC_ENCHANTBLADE)->val2; + if (sstatus->matk_max > sstatus->matk_min) + enchant_dmg = enchant_dmg + sstatus->matk_min + rnd() % (sstatus->matk_max - sstatus->matk_min); + else + enchant_dmg = enchant_dmg + sstatus->matk_min; + enchant_dmg = enchant_dmg - (tstatus->mdef + tstatus->mdef2); + if (enchant_dmg > 0) + ATK_ADD(wd->damage, wd->damage2, enchant_dmg); + } + } + if (skill_id != SN_SHARPSHOOTING && skill_id != RA_ARROWSTORM) + status_change_end(src, SC_CAMOUFLAGE); + } + +#ifndef RENEWAL + 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, wd->miscflag); + + wd->damage += md.damage; + } +#endif + + // Skill damage adjustment + if ((skill_damage = battle_skill_damage(src, target, skill_id)) != 0) + ATK_ADDRATE(wd->damage, wd->damage2, skill_damage); +} + +/*==================================================== + * Basic wd init - not influenced by HIT/MISS/DEF/etc. + *---------------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static struct Damage initialize_weapon_data(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, int wflag) +{ + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + status_change *sc = status_get_sc(src); + map_session_data *sd = BL_CAST(BL_PC, src); + struct Damage wd; + + wd.type = DMG_NORMAL; //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. + // counter attack DOES obey ASPD delay on official, uncomment if you want the old (bad) behavior [helvetica] + /*if(skill_id == KN_AUTOCOUNTER) + wd.amotion /= 2; */ + wd.dmotion = tstatus->dmotion; + wd.blewcount =skill_get_blewcount(skill_id,skill_lv); + wd.miscflag = wflag; + wd.flag = BF_WEAPON; //Initial Flag + wd.flag |= (skill_id||wd.miscflag)?BF_SKILL:BF_NORMAL; // Baphomet card's splash damage is counted as a skill. [Inkfish] + wd.isspdamage = false; + wd.damage = wd.damage2 = +#ifdef RENEWAL + wd.statusAtk = wd.statusAtk2 = wd.equipAtk = wd.equipAtk2 = wd.weaponAtk = wd.weaponAtk2 = wd.masteryAtk = wd.masteryAtk2 = + wd.percentAtk = wd.percentAtk2 = +#else + wd.basedamage = +#endif + 0; + + wd.dmg_lv=ATK_DEF; //This assumption simplifies the assignation later + + if(sd) + wd.blewcount += battle_blewcount_bonus(sd, skill_id); + + if (skill_id) { + wd.flag |= battle_range_type(src, target, skill_id, skill_lv); + switch(skill_id) + { +#ifdef RENEWAL + case RG_BACKSTAP: + if (sd && sd->status.weapon == W_DAGGER) + wd.div_ = 2; + break; + case MO_CHAINCOMBO: + if (sd && sd->status.weapon == W_KNUCKLE) + wd.div_ = -6; + break; +#endif + case MH_SONIC_CRAW:{ + TBL_HOM *hd = BL_CAST(BL_HOM,src); + wd.div_ = hd->homunculus.spiritball; + } + break; + case MO_FINGEROFFENSIVE: + if (sd) { + if (battle_config.finger_offensive_type) + wd.div_ = 1; +#ifndef RENEWAL + else if ((sd->spiritball + sd->spiritball_old) < wd.div_) + wd.div_ = sd->spiritball + sd->spiritball_old; +#endif + } + 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 = DMG_MULTI_HIT; + break; + + case GS_GROUNDDRIFT: + wd.amotion = sstatus->amotion; + [[fallthrough]]; + case KN_SPEARSTAB: +#ifndef RENEWAL + case KN_BOWLINGBASH: +#endif + case MS_BOWLINGBASH: + case MO_BALKYOUNG: + case TK_TURNKICK: + wd.blewcount = 0; + break; +#ifdef RENEWAL + case KN_BOWLINGBASH: + if (sd && sd->status.weapon == W_2HSWORD) { + if (wd.miscflag >= 2 && wd.miscflag <= 3) + wd.div_ = 3; + else if (wd.miscflag >= 4) + wd.div_ = 4; + } + break; +#endif + case KN_AUTOCOUNTER: + wd.flag = (wd.flag&~BF_SKILLMASK)|BF_NORMAL; + break; + case LK_SPIRALPIERCE: + if (!sd) wd.flag = (wd.flag&~(BF_RANGEMASK|BF_WEAPONMASK))|BF_LONG|BF_MISC; + break; + case RK_WINDCUTTER: + if (sd && (sd->status.weapon == W_1HSPEAR || sd->status.weapon == W_2HSPEAR)) + wd.flag |= BF_LONG; + break; + case NC_BOOSTKNUCKLE: + case NC_VULCANARM: + case NC_ARMSCANNON: + if (sc && sc->getSCE(SC_ABR_DUAL_CANNON)) + wd.div_ = 2; + break; + case NC_POWERSWING: + if (sc && sc->getSCE(SC_ABR_BATTLE_WARIOR)) + wd.div_ = -2; + break; + case GN_CARTCANNON: + if (sc && sc->getSCE(SC_BIONIC_WOODENWARRIOR)) + wd.div_ = 2; + break; + case DK_SERVANT_W_PHANTOM: + case DK_SERVANT_W_DEMOL: + if (sd && (sd->servantball + sd->servantball_old) < wd.div_) + wd.div_ = sd->servantball + sd->servantball_old; + break; + case IQ_THIRD_FLAME_BOMB: + wd.div_ = min(wd.div_ + wd.miscflag, 3); // Number of hits doesn't go above 3. + break; + case IG_OVERSLASH: + if( wd.miscflag >= 4 ){ + wd.div_ = 7; + }else if( wd.miscflag >= 2 ){ + wd.div_ = 5; + } + break; + case SHC_ETERNAL_SLASH: + if (sc && sc->getSCE(SC_E_SLASH_COUNT)) + wd.div_ = sc->getSCE(SC_E_SLASH_COUNT)->val1; + break; + case SHC_IMPACT_CRATER: + if (sc && sc->getSCE(SC_ROLLINGCUTTER)) + wd.div_ = sc->getSCE(SC_ROLLINGCUTTER)->val1; + break; + case MT_AXE_STOMP: + if (sd && sd->status.weapon == W_2HAXE) + wd.div_ = 3; + break; + case SHC_SAVAGE_IMPACT: + wd.div_ = wd.div_ + wd.miscflag; + break; + case MT_MIGHTY_SMASH: + if (sc && sc->getSCE(SC_AXE_STOMP)) + wd.div_ = 7; + break; + case BO_EXPLOSIVE_POWDER: + if (sc && sc->getSCE(SC_RESEARCHREPORT)) + wd.div_ = 5; + break; + case BO_MAYHEMIC_THORNS: + if (sc && sc->getSCE(SC_RESEARCHREPORT)) + wd.div_ = 4; + break; + case HN_DOUBLEBOWLINGBASH: + if (wd.miscflag > 1) + wd.div_ += min(4, wd.miscflag); + break; + } + } else { + bool is_long = false; + + if (is_skill_using_arrow(src, skill_id) || (sc && sc->getSCE(SC_SOULATTACK))) + is_long = true; + wd.flag |= is_long ? BF_LONG : BF_SHORT; + } + + return wd; +} + +/** + * Check if we should reflect the damage and calculate it if so + * @param attack_type : BL_WEAPON,BL_MAGIC or BL_MISC + * @param wd : weapon damage + * @param src : bl who did the attack + * @param target : target of the attack + * @param skill_id : id of casted skill, 0 = basic atk + * @param skill_lv : lvl of skill casted + */ +void battle_do_reflect(int attack_type, struct Damage *wd, struct block_list* src, struct block_list* target, uint16 skill_id, uint16 skill_lv) +{ + // Don't reflect your own damage (Grand Cross) + if ((wd->damage + wd->damage2) && src && target && src != target && (src->type != BL_SKILL || + (src->type == BL_SKILL && (skill_id == SG_SUN_WARM || skill_id == SG_MOON_WARM || skill_id == SG_STAR_WARM )))) + { + int64 damage = wd->damage + wd->damage2, rdamage = 0; + map_session_data *tsd = BL_CAST(BL_PC, target); + status_change *tsc = status_get_sc(target); + status_data* sstatus = status_get_status_data(*src); + struct unit_data *ud = unit_bl2ud(target); + t_tick tick = gettick(), rdelay = 0; + + if (!tsc) + return; + + auto * sce = tsc->getSCE(SC_MAXPAIN); + if (sce) { + sce->val2 = (int)damage; + if (!tsc->getSCE(SC_KYOMU) && !(tsc->getSCE(SC_DARKCROW) && (wd->flag&BF_SHORT))) //SC_KYOMU invalidates reflecting ability. SC_DARKCROW also does, but only for short weapon attack. + skill_castend_damage_id(target, src, NPC_MAXPAIN_ATK, sce->val1, tick, ((wd->flag & 1) ? wd->flag - 1 : wd->flag)); + } + + // Calculate skill reflect damage separately + if ((ud && !ud->immune_attack) || !status_bl_has_mode(target, MD_SKILLIMMUNE)) + rdamage = battle_calc_return_damage(target, src, &damage, wd->flag, skill_id,true); + if( rdamage > 0 ) { + struct block_list *d_bl = battle_check_devotion(src); + status_change *sc = status_get_sc(src); + + if (sc && sc->getSCE(SC_VITALITYACTIVATION)) + rdamage /= 2; + if( attack_type == BF_WEAPON && tsc->getSCE(SC_REFLECTDAMAGE) ) // 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,sstatus->dmotion,rdamage,wd->flag); + else if( attack_type == BF_WEAPON || attack_type == BF_MISC) { + rdelay = clif_damage(*src, (d_bl == nullptr) ? *src : *d_bl, tick, wd->amotion, sstatus->dmotion, rdamage, 1, DMG_ENDURE, 0, false); + if( tsd ) + battle_drain(tsd, src, rdamage, rdamage, sstatus->race, sstatus->class_); + // It appears that official servers give skill reflect damage a longer delay + battle_delay_damage(tick, wd->amotion, target, (!d_bl) ? src : d_bl, 0, CR_REFLECTSHIELD, 0, rdamage, ATK_DEF, rdelay ,true, false); + skill_additional_effect(target, (!d_bl) ? src : d_bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL, ATK_DEF, tick); + } + } + } +} + +/*============================================ + * Calculate "weapon"-type attacks and skills + *-------------------------------------------- + * Credits: + * Original coder Skotlex + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +static struct Damage battle_calc_weapon_attack(struct block_list *src, struct block_list *target, uint16 skill_id, uint16 skill_lv, int wflag) +{ + map_session_data *sd, *tsd; + struct Damage wd; + status_change *sc = status_get_sc(src); + status_change *tsc = status_get_sc(target); + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + int right_element, left_element; + bool infdef = false; + + memset(&wd,0,sizeof(wd)); + + if (src == nullptr || target == nullptr) { + nullpo_info(NLP_MARK); + return wd; + } + + wd = initialize_weapon_data(src, target, skill_id, skill_lv, wflag); + + right_element = battle_get_weapon_element(&wd, src, target, skill_id, skill_lv, EQI_HAND_R, false); + left_element = battle_get_weapon_element(&wd, src, target, skill_id, skill_lv, EQI_HAND_L, false); + + if (sc && !sc->count) + sc = nullptr; //Skip checking as there are no status changes active. + if (tsc && !tsc->count) + tsc = nullptr; //Skip checking as there are no status changes active. + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + + //Check for Lucky Dodge + if ((!skill_id || skill_id == PA_SACRIFICE) && tstatus->flee2 && rnd()%1000 < tstatus->flee2) { + wd.type = DMG_LUCY_DODGE; + wd.dmg_lv = ATK_LUCKY; + if(wd.div_ < 0) + wd.div_ *= -1; + return wd; + } + + // on official check for multi hit first so we can override crit on double attack [helvetica] + battle_calc_multi_attack(&wd, src, target, skill_id, skill_lv); + + // crit check is next since crits always hit on official [helvetica] + if (is_attack_critical(&wd, src, target, skill_id, skill_lv, true)) { +#if PACKETVER >= 20161207 + if (wd.type&DMG_MULTI_HIT) + wd.type = DMG_MULTI_HIT_CRITICAL; + else + wd.type = DMG_CRITICAL; +#else + wd.type = DMG_CRITICAL; +#endif + } + + std::bitset nk = battle_skill_get_damage_properties(skill_id, wd.miscflag); + + // check if we're landing a hit + if(!is_attack_hitting(&wd, src, target, skill_id, skill_lv, true)) + wd.dmg_lv = ATK_FLEE; + else if(!(infdef = is_infinite_defense(target, wd.flag))) { //no need for math against plants + +#ifndef RENEWAL + // First call function with skill_id 0 to get base damage of a normal attack + battle_calc_skill_base_damage(&wd, src, target, 0, 0); // base damage + wd.basedamage = wd.damage; + // Now get actual skill damage + if (skill_id != 0) +#endif + battle_calc_skill_base_damage(&wd, src, target, skill_id, skill_lv); // base skill damage + +#ifndef RENEWAL + // Skill ratio + ATK_RATE(wd.damage, wd.damage2, battle_calc_attack_skill_ratio(&wd, src, target, skill_id, skill_lv)); + + // Additive damage bonus + ATK_ADD(wd.damage, wd.damage2, battle_calc_skill_constant_addition(&wd, src, target, skill_id, skill_lv)); +#endif + +#ifdef RENEWAL + if(skill_id == HW_MAGICCRASHER) { // Add weapon attack for MATK onto Magic Crasher + status_data* sstatus = status_get_status_data(*src); + + if (sstatus->matk_max > sstatus->matk_min) { + ATK_ADD(wd.weaponAtk, wd.weaponAtk2, sstatus->matk_min+rnd()%(sstatus->matk_max-sstatus->matk_min)); + } else + ATK_ADD(wd.weaponAtk, wd.weaponAtk2, sstatus->matk_min); + } +#endif + + int i = 0; + +#ifndef RENEWAL + // add any miscellaneous player ATK bonuses + if( sd && skill_id && (i = pc_skillatk_bonus(sd, skill_id))) + ATK_ADDRATE(wd.damage, wd.damage2, i); + if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id))) + ATK_ADDRATE(wd.damage, wd.damage2, -i); +#endif + +#ifdef RENEWAL + // In Renewal we only cardfix to the weapon and equip ATK + //Card Fix for attacker (sd), 2 is added to the "left" flag meaning "attacker cards only" + if (sd) { + wd.weaponAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk, 2, wd.flag); + wd.equipAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk, 2, wd.flag); + if (is_attack_left_handed(src, skill_id)) { + wd.weaponAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk2, 3, wd.flag); + wd.equipAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk2, 3, wd.flag); + } + } + + //Card Fix for target (tsd), 2 is not added to the "left" flag meaning "target cards only" + if (tsd && skill_id != NJ_ISSEN && skill_id != GN_FIRE_EXPANSION_ACID) { // These skills will do a card fix later + std::bitset ignoreele_nk = nk; + + ignoreele_nk.set(NK_IGNOREELEMENT); + wd.statusAtk += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.statusAtk, 0, wd.flag); + wd.weaponAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk, 0, wd.flag); + wd.equipAtk += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk, 0, wd.flag); + wd.masteryAtk += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.masteryAtk, 0, wd.flag); + if (is_attack_left_handed(src, skill_id)) { + wd.statusAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.statusAtk2, 1, wd.flag); + wd.weaponAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.weaponAtk2, 1, wd.flag); + wd.equipAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.equipAtk2, 1, wd.flag); + wd.masteryAtk2 += battle_calc_cardfix(BF_WEAPON, src, target, ignoreele_nk, right_element, left_element, wd.masteryAtk2, 1, wd.flag); + } + } + + // final attack bonuses that aren't affected by cards + battle_attack_sc_bonus(&wd, src, target, skill_id, skill_lv); + + if (sd) { //monsters, homuns and pets have their damage computed directly + wd.damage = wd.statusAtk + wd.weaponAtk + wd.equipAtk + wd.percentAtk; + if( is_attack_left_handed( src, skill_id ) ){ + wd.damage2 = wd.statusAtk2 + wd.weaponAtk2 + wd.equipAtk2 + wd.percentAtk2; + } + // Apply P.ATK mod + // But for Dragonbreaths it only applies if Dragonic Aura is skilled + if( ( skill_id != RK_DRAGONBREATH && skill_id != RK_DRAGONBREATH_WATER ) || pc_checkskill( sd, DK_DRAGONIC_AURA ) > 0 ){ + wd.damage = (int64)floor( (float)( wd.damage * ( 100 + sstatus->patk ) / 100 ) ); + if( is_attack_left_handed( src, skill_id ) ){ + wd.damage2 = (int64)floor( (float)( wd.damage2 * ( 100 + sstatus->patk ) / 100 ) ); + } + } + // Apply MasteryATK + wd.damage += wd.masteryAtk; + if( is_attack_left_handed( src, skill_id ) ){ + wd.damage2 += wd.masteryAtk2; + } + + // CritAtkRate modifier + if (wd.type == DMG_CRITICAL || wd.type == DMG_MULTI_HIT_CRITICAL) { + if (skill_id > 0) { + wd.damage += (int64)floor((float)(wd.damage * sd->bonus.crit_atk_rate / 200)); + if (is_attack_left_handed(src, skill_id)) + wd.damage2 += (int64)floor((float)(wd.damage2 * sd->bonus.crit_atk_rate / 200)); + } + else { + wd.damage += (int64)floor((float)(wd.damage * sd->bonus.crit_atk_rate / 100)); + if (is_attack_left_handed(src, skill_id)) + wd.damage2 += (int64)floor((float)(wd.damage2 * sd->bonus.crit_atk_rate / 100)); + } + } + + if (wd.flag & BF_SHORT) + ATK_ADDRATE(wd.damage, wd.damage2, sd->bonus.short_attack_atk_rate); + if(wd.flag&BF_LONG && (skill_id != RA_WUGBITE && skill_id != RA_WUGSTRIKE)) //Long damage rate addition doesn't use weapon + equip attack + ATK_ADDRATE(wd.damage, wd.damage2, sd->bonus.long_attack_atk_rate); + } + + // Skill ratio + ATK_RATE(wd.damage, wd.damage2, battle_calc_attack_skill_ratio(&wd, src, target, skill_id, skill_lv)); + + // Additive damage bonus + ATK_ADD(wd.damage, wd.damage2, battle_calc_skill_constant_addition(&wd, src, target, skill_id, skill_lv)); + + // Advance Katar Mastery + if (sd) { + uint16 katar_skill; + + if (sd->status.weapon == W_KATAR && (katar_skill = pc_checkskill(sd, ASC_KATAR)) > 0) // Adv. Katar Mastery applied after calculate with skillratio. + ATK_ADDRATE(wd.damage, wd.damage2, (10 + 2 * katar_skill)); + } + + // Res reduces physical damage by a percentage and + // is calculated before DEF and other reductions. + // All skills that use the simple defense formula (damage substracted by DEF+DEF2) ignore Res + // TODO: Res formula probably should be: (2000+x)/(2000+5x), but with the reduction rounded down + if ((wd.damage + wd.damage2) && tstatus->res > 0 && !nk[NK_SIMPLEDEFENSE]) { + short res = tstatus->res; + short ignore_res = 0;// Value used as a percentage. + + if (sd) + ignore_res += sd->indexed_bonus.ignore_res_by_race[tstatus->race] + sd->indexed_bonus.ignore_res_by_race[RC_ALL]; + + // Attacker status's that pierce Res. + if (sc) { + if (sc->getSCE(SC_A_TELUM)) + ignore_res += sc->getSCE(SC_A_TELUM)->val2; + if (sc->getSCE(SC_POTENT_VENOM)) + ignore_res += sc->getSCE(SC_POTENT_VENOM)->val2; + } + + ignore_res = min(ignore_res, battle_config.max_res_mres_ignored); + + if (ignore_res > 0) + res -= res * ignore_res / 100; + + // Apply damage reduction. + wd.damage = wd.damage * (5000 + res) / (5000 + 10 * res); + wd.damage2 = wd.damage2 * (5000 + res) / (5000 + 10 * res); + } + +#else + // final attack bonuses that aren't affected by cards + battle_attack_sc_bonus(&wd, src, target, skill_id, skill_lv); +#endif + + if (wd.damage + wd.damage2) { +#ifdef RENEWAL + // Check if attack ignores DEF (in pre-renewal we need to update base damage even when the skill ignores DEF) + if(!attack_ignores_def(&wd, src, target, skill_id, skill_lv, EQI_HAND_L) || !attack_ignores_def(&wd, src, target, skill_id, skill_lv, EQI_HAND_R)) +#else + // Shield Boomerang and Rapid Smiting already calculated the defense before the skill ratio was applied + if(skill_id != PA_SHIELDCHAIN && skill_id != CR_SHIELDBOOMERANG) +#endif + battle_calc_defense_reduction(&wd, src, target, skill_id, skill_lv); + + battle_calc_attack_post_defense(&wd, src, target, skill_id, skill_lv); + } + +#ifdef RENEWAL + // add any miscellaneous player ATK bonuses + if( sd && skill_id && (i = pc_skillatk_bonus(sd, skill_id))) { + ATK_ADDRATE(wd.damage, wd.damage2, i); + RE_ALLATK_ADDRATE(&wd, i); + } + if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id))) { + ATK_ADDRATE(wd.damage, wd.damage2, -i); + RE_ALLATK_ADDRATE(&wd, -i); + } +#endif + } + + battle_calc_element_damage(&wd, src, target, skill_id, skill_lv); + +#ifdef RENEWAL + if (is_attack_critical(&wd, src, target, skill_id, skill_lv, false)) { + if (sd) { //Check for player so we don't crash out, monsters don't have bonus crit rates [helvetica] + wd.damage = (int64)floor((float)((wd.damage * (1.4f + (0.01f * sstatus->crate))))); + if (is_attack_left_handed(src, skill_id)) + wd.damage2 = (int64)floor((float)((wd.damage2 * (1.4f + (0.01f * sstatus->crate))))); + } else + wd.damage = (int64)floor((float)(wd.damage * 1.4f)); + + if (tsd && tsd->bonus.crit_def_rate != 0) + ATK_ADDRATE(wd.damage, wd.damage2, -tsd->bonus.crit_def_rate); + } +#endif + + switch (skill_id) { + case TK_DOWNKICK: + case TK_STORMKICK: + case TK_TURNKICK: + case TK_COUNTER: + if(sd && sd->weapontype1 == W_FIST && sd->weapontype2 == W_FIST) + ATK_ADD(wd.damage, wd.damage2, 10 * pc_checkskill(sd, TK_RUN)); + break; + case LG_SHIELDPRESS: + if (sd) { + int damagevalue = 0; + short index = sd->equip_index[EQI_HAND_L]; + + if (index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == IT_ARMOR) + damagevalue = sstatus->vit * sd->inventory.u.items_inventory[index].refine; + ATK_ADD(wd.damage, wd.damage2, damagevalue); + } + break; + case SR_TIGERCANNON: + // (Tiger Cannon skill level x 240) + (Target Base Level x 40) + if (wd.miscflag&8) { + ATK_ADD(wd.damage, wd.damage2, skill_lv * 500 + status_get_lv(target) * 40); + } else + ATK_ADD(wd.damage, wd.damage2, skill_lv * 240 + status_get_lv(target) * 40); + break; + case SR_GATEOFHELL: { + status_data* sstatus = status_get_status_data(*src); + double bonus = 1 + skill_lv * 2 / 10; + + ATK_ADD(wd.damage, wd.damage2, sstatus->max_hp - sstatus->hp); + if(sc && sc->getSCE(SC_COMBO) && sc->getSCE(SC_COMBO)->val1 == SR_FALLENEMPIRE) { + ATK_ADD(wd.damage, wd.damage2, static_cast(sstatus->max_sp * bonus) + 40 * status_get_lv(src)); + } else + ATK_ADD(wd.damage, wd.damage2, static_cast(sstatus->sp * bonus) + 10 * status_get_lv(src)); + } + break; + case MH_TINDER_BREAKER: + ATK_ADD(wd.damage, wd.damage2, 2500 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus + break; + case MH_CBC: + ATK_ADD(wd.damage, wd.damage2, 4000 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus + break; + case MH_EQC: + ATK_ADD(wd.damage, wd.damage2, 6000 * skill_lv + status_get_lv(src)); // !TODO: Confirm base level bonus + break; + case NPC_MAXPAIN_ATK: + if (sc) { + auto * sce = sc->getSCE(SC_MAXPAIN); + if (sce) { + if (sce->val2) + wd.damage = sce->val2 * skill_lv / 10; + else if (sce->val3) + wd.damage = sce->val3 * skill_lv / 10; + } + } + else + wd.damage = 0; + break; + } + + if(sd) { +#ifndef RENEWAL + uint16 skill; + + if ((skill = pc_checkskill(sd, BS_WEAPONRESEARCH)) > 0) + ATK_ADD(wd.damage, wd.damage2, skill * 2); + if (skill_id == GS_GROUNDDRIFT) + ATK_ADD(wd.damage, wd.damage2, 50 * skill_lv); + if (skill_id != MC_CARTREVOLUTION && pc_checkskill(sd, BS_HILTBINDING) > 0) + ATK_ADD(wd.damage, wd.damage2, 4); + + //Card Fix for attacker (sd), 2 is added to the "left" flag meaning "attacker cards only" + switch(skill_id) { + case RK_DRAGONBREATH: + case RK_DRAGONBREATH_WATER: + if(wd.flag&BF_LONG) { //Add check here, because we want to apply the same behavior in pre-renewal [exneval] + wd.damage = wd.damage * (100 + sd->bonus.long_attack_atk_rate) / 100; + if(is_attack_left_handed(src, skill_id)) + wd.damage2 = wd.damage2 * (100 + sd->bonus.long_attack_atk_rate) / 100; + } + break; + default: + wd.damage += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage, 2, wd.flag); + if( is_attack_left_handed(src, skill_id )) + wd.damage2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage2, 3, wd.flag); + break; + } +#endif + } + +#ifdef RENEWAL + // In renewal only do it for non player attacks + if( tsd && !sd ){ +#else + if( tsd ){ +#endif + // Card Fix for target (tsd), 2 is not added to the "left" flag meaning "target cards only" + wd.damage += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage, 0, wd.flag); + if(is_attack_left_handed(src, skill_id)) + wd.damage2 += battle_calc_cardfix(BF_WEAPON, src, target, nk, right_element, left_element, wd.damage2, 1, wd.flag); + } + + // only do 1 dmg to plant, no need to calculate rest + if(infdef){ + battle_calc_attack_plant(&wd, src, target, skill_id, skill_lv); + return wd; + } + + //Apply DAMAGE_DIV_FIX and check for min damage + battle_apply_div_fix(&wd, skill_id); + + battle_calc_attack_left_right_hands(&wd, src, target, skill_id, skill_lv); + +#ifdef RENEWAL + switch (skill_id) { + case NJ_ISSEN: + case GN_FIRE_EXPANSION_ACID: + return wd; //These skills will do a GVG fix later + default: +#endif + battle_calc_attack_gvg_bg(&wd, src, target, skill_id, skill_lv); +#ifdef RENEWAL + break; + } +#endif + + battle_calc_weapon_final_atk_modifiers(&wd, src, target, skill_id, skill_lv); + + battle_absorb_damage(target, &wd); + + battle_do_reflect(BF_WEAPON,&wd, src, target, skill_id, skill_lv); //WIP [lighta] + + return wd; +} + +/*========================================== + * Calculate "magic"-type attacks and skills + *------------------------------------------ + * Credits: + * Original coder DracoRPG + * Refined and optimized by helvetica + */ +struct Damage battle_calc_magic_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag) +{ + int i, skill_damage = 0; + short s_ele = 0; + + TBL_PC *sd; + TBL_PC *tsd; + status_change *sc, *tsc; + struct Damage ad; + struct { + unsigned imdef : 1; + unsigned infdef : 1; + } flag; + + memset(&ad,0,sizeof(ad)); + memset(&flag,0,sizeof(flag)); + + if (src == nullptr || target == nullptr) { + nullpo_info(NLP_MARK); + return ad; + } + + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + + // Initial Values + // Set to 1 because magic damage on plants is 1 per hit; if target is not a plant this gets reinitialized to 0 later + 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.miscflag = mflag; + ad.dmg_lv = ATK_DEF; + + std::shared_ptr skill = skill_db.find(skill_id); + std::bitset nk; + + if (skill) + nk = skill->nk; + + flag.imdef = nk[NK_IGNOREDEFENSE] ? 1 : 0; + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + s_elemental_data* ed = BL_CAST(BL_ELEM, src); + sc = status_get_sc(src); + tsc = status_get_sc(target); + + //Initialize variables that will be used afterwards + s_ele = battle_get_magic_element(src, target, skill_id, skill_lv, mflag); + + switch(skill_id) { + case WL_HELLINFERNO: + if (mflag & 2) { // ELE_DARK + ad.div_ = 3; + } + break; + case NPC_PSYCHIC_WAVE: + case SO_PSYCHIC_WAVE: + if (sd && (sd->weapontype1 == W_STAFF || sd->weapontype1 == W_2HSTAFF || sd->weapontype1 == W_BOOK)) + ad.div_ = 2; + break; + case AG_DESTRUCTIVE_HURRICANE: + if (sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 2) + ad.blewcount = 2; + break; + case AG_CRYSTAL_IMPACT: + if (sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 2) + ad.div_ = 2; + break; + case ABC_ABYSS_SQUARE: + if (mflag == 2) + ad.div_ = 2; + break; + case AG_CRIMSON_ARROW_ATK: + if( sc != nullptr && sc->getSCE( SC_CLIMAX ) ){ + ad.div_ = 2; + } + break; + } + + //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); + + //Infinite defense (plant mode) + flag.infdef = is_infinite_defense(target, ad.flag)?1:0; + + switch(skill_id) { + case MG_FIREWALL: + if ( tstatus->def_ele == ELE_FIRE || battle_check_undead(tstatus->race, tstatus->def_ele) ) + ad.blewcount = 0; //No knockback + [[fallthrough]]; + case NJ_KAENSIN: + case PR_SANCTUARY: + ad.dmotion = 1; //No flinch animation. + break; + } + + if (!flag.infdef) { //No need to do the math for plants + unsigned int skillratio = 100; //Skill dmg modifiers. + //Damage was set to 1 to simulate plant damage; if not plant, need to reinitialize damage with 0 + ad.damage = 0; +//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; } + + //Calc base damage according to skill + switch (skill_id) { + case AL_HEAL: + case PR_BENEDICTIO: + case PR_SANCTUARY: + 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 && !status_has_mode(tstatus,MD_STATUSIMMUNE)) + 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 NPC_DARKBREATH: + ad.damage = tstatus->hp * (skill_lv <= 5 ? 100 / (2 * (6 - skill_lv)) : 50) / 100; + break; + case PF_SOULBURN: + ad.damage = tstatus->sp * 2; + break; + case AB_RENOVATIO: + ad.damage = status_get_lv(src) * 10 + sstatus->int_; + break; + case NPC_EARTHQUAKE: + if (mflag & NPC_EARTHQUAKE_FLAG) { + ad.flag |= NPC_EARTHQUAKE_FLAG; // Pass flag to battle_calc_damage + mflag &= ~NPC_EARTHQUAKE_FLAG; // Remove before NK_SPLASHSPLIT check + } + + if (src->type == BL_PC) + ad.damage = sstatus->str * 2 + battle_calc_weapon_attack(src, target, skill_id, skill_lv, mflag).damage; + else + ad.damage = battle_calc_base_damage(src, sstatus, &sstatus->rhw, sc, tstatus->size, 0); + + MATK_RATE(200 + 100 * skill_lv + 100 * (skill_lv / 2) + ((skill_lv > 4) ? 100 : 0)); + + if (nk[NK_SPLASHSPLIT] && mflag > 1) + ad.damage /= mflag; + break; + case NPC_ICEMINE: + case NPC_FLAMECROSS: + ad.damage = static_cast( sstatus->rhw.atk ) * static_cast( 20 ) * static_cast( skill_lv ); + 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 (sd) { // Soul energy spheres adds MATK. + MATK_ADD(3*sd->soulball); + } + + 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 += -30 + 10 * skill_lv; + break; + case MG_FIREBALL: +#ifdef RENEWAL + skillratio += 40 + 20 * skill_lv; + if(ad.miscflag == 2) //Enemies at the edge of the area will take 75% of the damage + skillratio = skillratio * 3 / 4; +#else + skillratio += -30 + 10 * skill_lv; +#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) { + if ((skill_id == MG_FIREBOLT && sc->getSCE(SC_FLAMETECHNIC_OPTION)) || + (skill_id == MG_COLDBOLT && sc->getSCE(SC_COLD_FORCE_OPTION)) || + (skill_id == MG_LIGHTNINGBOLT && sc->getSCE(SC_GRACE_BREEZE_OPTION))) + skillratio *= 5; + + if (sc->getSCE(SC_SPELLFIST) && mflag & BF_SHORT) { + skillratio += (sc->getSCE(SC_SPELLFIST)->val3 * 100) + (sc->getSCE(SC_SPELLFIST)->val1 * 50 - 50) - 100; // val3 = used bolt level, val1 = 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 = DMG_NORMAL; + } + } + 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.getSCE(SC_SPIRIT) && sd->sc.getSCE(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 + (100 + skill_lv * 10) * 2 / 3; + break; + case WZ_FIREPILLAR: + if (sd && ad.div_ > 0) + ad.div_ *= -1; //For players, damage is divided by number of hits + skillratio += -60 + 20 * skill_lv; //20% MATK each hit + break; + case WZ_SIGHTRASHER: + skillratio += 20 * skill_lv; + break; + case WZ_WATERBALL: + skillratio += 30 * skill_lv; + break; + case WZ_STORMGUST: +#ifdef RENEWAL + skillratio -= 30; // Offset only once + skillratio += 50 * skill_lv; +#else + skillratio += 40 * skill_lv; +#endif + break; +#ifdef RENEWAL + case WZ_EARTHSPIKE: + skillratio += 100; + if (sc && sc->getSCE(SC_EARTH_CARE_OPTION)) + skillratio += skillratio * 800 / 100; + break; +#endif + case HW_NAPALMVULCAN: +#ifdef RENEWAL + skillratio += -100 + 70 * skill_lv; + RE_LVL_DMOD(100); +#else + skillratio += 25; +#endif + break; + case SL_STIN: //Target size must be small (0) for full damage + skillratio += (tstatus->size != SZ_SMALL ? -99 : 10 * skill_lv); + break; + case SL_STUN: + skillratio += 5 * skill_lv; + break; + case SL_SMA: //Base damage is 40% + lv% + skillratio += -60 + status_get_lv(src); + break; + case NJ_KOUENKA: + skillratio -= 10; + if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0) + skillratio += 10 * sd->spiritcharm; + break; + case NJ_KAENSIN: + skillratio -= 50; + if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0) + skillratio += 20 * sd->spiritcharm; + break; + case NJ_BAKUENRYU: + skillratio += 50 + 150 * skill_lv; + if(sd && sd->spiritcharm_type == CHARM_TYPE_FIRE && sd->spiritcharm > 0) + skillratio += 100 * sd->spiritcharm; + break; + case NJ_HYOUSENSOU: +#ifdef RENEWAL + skillratio -= 30; + if (sc && sc->getSCE(SC_SUITON)) + skillratio += 2 * skill_lv; +#endif + if(sd && sd->spiritcharm_type == CHARM_TYPE_WATER && sd->spiritcharm > 0) + skillratio += 20 * sd->spiritcharm; + break; + case NJ_HYOUSYOURAKU: + skillratio += 50 * skill_lv; + if(sd && sd->spiritcharm_type == CHARM_TYPE_WATER && sd->spiritcharm > 0) + skillratio += 100 * sd->spiritcharm; + break; + case NJ_RAIGEKISAI: +#ifdef RENEWAL + skillratio += 100 * skill_lv; +#else + skillratio += 60 + 40 * skill_lv; +#endif + if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0) + skillratio += 20 * sd->spiritcharm; + break; + case NJ_KAMAITACHI: + skillratio += 100 * skill_lv; + if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0) + skillratio += 100 * sd->spiritcharm; + break; + case NJ_HUUJIN: +#ifdef RENEWAL + skillratio += 50; +#endif + if(sd && sd->spiritcharm_type == CHARM_TYPE_WIND && sd->spiritcharm > 0) + skillratio += 10 * sd->spiritcharm; + break; + case NPC_ENERGYDRAIN: + skillratio += 100 * skill_lv; + break; +#ifdef RENEWAL + case WZ_HEAVENDRIVE: + case NPC_GROUNDDRIVE: + skillratio += 25; + break; + case WZ_METEOR: + skillratio += 25; + break; + case WZ_VERMILION: + if(sd) + skillratio += 300 + skill_lv * 100; + else + skillratio += 20 * skill_lv - 20; //Monsters use old formula + break; + case PR_MAGNUS: + if (battle_check_undead(tstatus->race, tstatus->def_ele) || tstatus->race == RC_DEMON) + skillratio += 30; + break; + case BA_DISSONANCE: + skillratio += skill_lv * 10; + if (sd) + skillratio += 3 * pc_checkskill(sd, BA_MUSICALLESSON); + break; + case HW_GRAVITATION: + skillratio += -100 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case PA_PRESSURE: + skillratio += -100 + 500 + 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case WZ_SIGHTBLASTER: + skillratio += 500; + break; +#else + case WZ_VERMILION: + skillratio += 20 * skill_lv - 20; + break; +#endif + case AB_JUDEX: + skillratio += -100 + 300 + 70 * skill_lv; + RE_LVL_DMOD(100); + break; + case AB_ADORAMUS: + skillratio += - 100 + 300 + 250 * skill_lv; + RE_LVL_DMOD(100); + break; + case AB_DUPLELIGHT_MAGIC: + skillratio += 300 + 40 * skill_lv; + break; + case WL_SOULEXPANSION: + skillratio += -100 + 1000 + skill_lv * 200 + sstatus->int_ / 6; // !TODO: Confirm INT bonus + RE_LVL_DMOD(100); + break; + case WL_FROSTMISTY: + skillratio += -100 + 200 + 100 * skill_lv; + RE_LVL_DMOD(100); + break; + case NPC_JACKFROST: + if (tsc && tsc->getSCE(SC_FREEZING)) { + skillratio += 900 + 300 * skill_lv; + RE_LVL_DMOD(100); + } else { + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(150); + } + break; + case WL_JACKFROST: + if (tsc && tsc->getSCE(SC_MISTY_FROST)) + skillratio += -100 + 1200 + 600 * skill_lv; + else + skillratio += -100 + 1000 + 300 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_DRAINLIFE: + skillratio += -100 + 200 * skill_lv + sstatus->int_; + RE_LVL_DMOD(100); + break; + case WL_CRIMSONROCK: + skillratio += -100 + 700 + 600 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_HELLINFERNO: + skillratio += -100 + 400 * skill_lv; + if (mflag & 2) // ELE_DARK + skillratio += 200; + RE_LVL_DMOD(100); + break; + case WL_COMET: + skillratio += -100 + 2500 + 700 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_CHAINLIGHTNING_ATK: + skillratio += 400 + 100 * skill_lv; + RE_LVL_DMOD(100); + if (mflag > 0) + skillratio += 100 * mflag; + break; + case WL_EARTHSTRAIN: + skillratio += -100 + 1000 + 600 * skill_lv; + RE_LVL_DMOD(100); + break; + case WL_TETRAVORTEX_FIRE: + case WL_TETRAVORTEX_WATER: + case WL_TETRAVORTEX_WIND: + case WL_TETRAVORTEX_GROUND: + skillratio += -100 + 800 + 400 * skill_lv; + break; + case WL_SUMMON_ATK_FIRE: + case WL_SUMMON_ATK_WATER: + case WL_SUMMON_ATK_WIND: + case WL_SUMMON_ATK_GROUND: + skillratio += 200; + RE_LVL_DMOD(100); + break; + case LG_RAYOFGENESIS: + skillratio += -100 + 350 * skill_lv + sstatus->int_; // !TODO: What's the INT bonus? + RE_LVL_DMOD(100); + break; + case NPC_RAYOFGENESIS: + skillratio += -100 + 200 * skill_lv; + break; + case WM_METALICSOUND: + skillratio += -100 + 120 * skill_lv + 60 * ((sd) ? pc_checkskill(sd, WM_LESSON) : 1); + if (tsc && tsc->getSCE(SC_SLEEP)) + skillratio += 100; // !TODO: Confirm target sleeping bonus + RE_LVL_DMOD(100); + if (tsc && tsc->getSCE(SC_SOUNDBLEND)) + skillratio += skillratio * 50 / 100; + break; + case WM_REVERBERATION: + // MATK [{(Skill Level x 300) + 400} x Casters Base Level / 100] % + skillratio += -100 + 700 + 300 * skill_lv; + RE_LVL_DMOD(100); + if (tsc && tsc->getSCE(SC_SOUNDBLEND)) + skillratio += skillratio * 50 / 100; + break; + case SO_FIREWALK: + skillratio += -100 + 60 * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->getSCE(SC_HEATER_OPTION) ) + skillratio += (sd ? sd->status.job_level / 2 : 0); + break; + case SO_ELECTRICWALK: + skillratio += -100 + 60 * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->getSCE(SC_BLAST_OPTION) ) + skillratio += (sd ? sd->status.job_level / 2 : 0); + break; + case NPC_FIREWALK: + case NPC_ELECTRICWALK: + skillratio += -100 + 100 * skill_lv; + break; + case SO_EARTHGRAVE: + skillratio += -100 + 2 * sstatus->int_ + 300 * pc_checkskill(sd, SA_SEISMICWEAPON) + sstatus->int_ * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->getSCE(SC_CURSED_SOIL_OPTION) ) + skillratio += (sd ? sd->status.job_level * 5 : 0); + break; + case SO_DIAMONDDUST: + skillratio += -100 + 2 * sstatus->int_ + 300 * pc_checkskill(sd, SA_FROSTWEAPON) + sstatus->int_ * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->getSCE(SC_COOLER_OPTION) ) + skillratio += (sd ? sd->status.job_level * 5 : 0); + break; + case SO_POISON_BUSTER: + skillratio += -100 + 1000 + 300 * skill_lv + sstatus->int_ / 6; // !TODO: Confirm INT bonus + if( tsc && tsc->getSCE(SC_CLOUD_POISON) ) + skillratio += 200 * skill_lv; + RE_LVL_DMOD(100); + if( sc && sc->getSCE(SC_CURSED_SOIL_OPTION) ) + skillratio += (sd ? sd->status.job_level * 5 : 0); + break; + case NPC_POISON_BUSTER: + skillratio += -100 + 1500 * skill_lv; + break; + case SO_PSYCHIC_WAVE: + skillratio += -100 + 70 * skill_lv + 3 * sstatus->int_; + RE_LVL_DMOD(100); + if (sc && (sc->getSCE(SC_HEATER_OPTION) || sc->getSCE(SC_COOLER_OPTION) || + sc->getSCE(SC_BLAST_OPTION) || sc->getSCE(SC_CURSED_SOIL_OPTION))) + skillratio += 20; + break; + case NPC_PSYCHIC_WAVE: + skillratio += -100 + 500 * skill_lv; + break; + case SO_CLOUD_KILL: + skillratio += -100 + 40 * skill_lv; + RE_LVL_DMOD(100); + if (sc) { + if (sc->getSCE(SC_CURSED_SOIL_OPTION)) + skillratio += (sd ? sd->status.job_level : 0); + + if (sc->getSCE(SC_DEEP_POISONING_OPTION)) + skillratio += skillratio * 1500 / 100; + } + break; + case NPC_CLOUD_KILL: + skillratio += -100 + 50 * skill_lv; + break; + case SO_VARETYR_SPEAR: + skillratio += -100 + (2 * sstatus->int_ + 150 * (pc_checkskill(sd, SO_STRIKING) + pc_checkskill(sd, SA_LIGHTNINGLOADER)) + sstatus->int_ * skill_lv / 2) / 3; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_BLAST_OPTION)) + skillratio += (sd ? sd->status.job_level * 5 : 0); + break; + case GN_DEMONIC_FIRE: + if (skill_lv > 20) // Fire expansion Lv.2 + skillratio += 10 + 20 * (skill_lv - 20) + status_get_int(src) * 10; + else if (skill_lv > 10) { // Fire expansion Lv.1 + skillratio += 10 + 20 * (skill_lv - 10) + status_get_int(src) + ((sd) ? sd->status.job_level : 50); + RE_LVL_DMOD(100); + } else + skillratio += 10 + 20 * skill_lv; + break; + case KO_KAIHOU: + if(sd && sd->spiritcharm_type != CHARM_TYPE_NONE && sd->spiritcharm > 0) { + skillratio += -100 + 200 * sd->spiritcharm; + RE_LVL_DMOD(100); + pc_delspiritcharm(sd, sd->spiritcharm, sd->spiritcharm_type); + } + 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: + case MH_XENO_SLASHER: + skillratio += -100 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->int_; // !TODO: Confirm Base Level and INT bonus + break; + case MH_TWISTER_CUTTER: + skillratio += -100 + 480 * skill_lv * status_get_lv(src) / 100 + sstatus->int_; // !TODO: Confirm Base Level and INT bonus + break; + case MH_ABSOLUTE_ZEPHYR: + skillratio += -100 + 1000 + 450 * skill_lv * status_get_lv(src) / 100 + sstatus->int_; // !TODO: Confirm Base Level and INT bonus + break; + case MH_HEILIGE_STANGE: + skillratio += -100 + 1500 + 250 * skill_lv * status_get_lv(src) / 150 + sstatus->vit; // !TODO: Confirm VIT bonus + break; + case MH_HEILIGE_PFERD: + skillratio += -100 + 1200 + 350 * skill_lv * status_get_lv(src) / 100 + sstatus->vit; // !TODO: Confirm VIT bonus + break; + case MH_POISON_MIST: + skillratio += -100 + 200 * skill_lv * status_get_lv(src) / 100 + sstatus->dex; // ! TODO: Confirm DEX bonus + break; + case SU_SV_STEMSPEAR: + skillratio += 600; + break; + case SU_CN_METEOR: + case SU_CN_METEOR2: + skillratio += 100 + 100 * skill_lv + sstatus->int_ * 5; // !TODO: Confirm INT bonus + RE_LVL_DMOD(100); + break; + case NPC_VENOMFOG: + skillratio += 600 + 100 * skill_lv; + break; + case NPC_COMET: + i = (sc ? distance_xy(target->x, target->y, sc->comet_x, sc->comet_y) : 8) / 2; + i = cap_value(i, 1, 4); + skillratio = 2500 + ((skill_lv - i + 1) * 500); + break; + case NPC_FIRESTORM: + skillratio += 200; + break; + case NPC_HELLBURNING: + skillratio += 900; + break; + case NPC_PULSESTRIKE2: + skillratio += 100; + break; + case SP_CURSEEXPLOSION: + if (tsc && tsc->getSCE(SC_SOULCURSE)) + skillratio += 1400 + 200 * skill_lv; + else + skillratio += 300 + 100 * skill_lv; + break; + case SP_SPA: + skillratio += 400 + 250 * skill_lv; + RE_LVL_DMOD(100); + break; + case SP_SHA: + skillratio += -100 + 5 * skill_lv; + break; + case SP_SWHOO: + skillratio += 1000 + 200 * skill_lv; + RE_LVL_DMOD(100); + break; + case NPC_STORMGUST2: + skillratio += 200 * skill_lv; + break; + case AG_DEADLY_PROJECTION: + skillratio += -100 + 2800 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_DESTRUCTIVE_HURRICANE: + skillratio += -100 + 600 + 2850 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CLIMAX)) + { + if (sc->getSCE(SC_CLIMAX)->val1 == 3) + skillratio += skillratio * 150 / 100; + else if (sc->getSCE(SC_CLIMAX)->val1 == 5) + skillratio -= skillratio * 20 / 100; + } + break; + case AG_RAIN_OF_CRYSTAL: + skillratio += -100 + 180 + 760 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_MYSTERY_ILLUSION: + skillratio += -100 + 950 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_VIOLENT_QUAKE_ATK: + skillratio += -100 + 200 + 1200 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CLIMAX)) { + if (sc->getSCE(SC_CLIMAX)->val1 == 1) + skillratio /= 2; + else if (sc->getSCE(SC_CLIMAX)->val1 == 3) + skillratio *= 3; + } + break; + case AG_SOUL_VC_STRIKE: + skillratio += -100 + 300 * skill_lv + 3 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_STRANTUM_TREMOR: + skillratio += -100 + 100 + 730 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_ALL_BLOOM_ATK: + skillratio += -100 + 200 + 1200 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CLIMAX)) { + if (sc->getSCE(SC_CLIMAX)->val1 == 3) + skillratio *= 4; + } + break; + case AG_ALL_BLOOM_ATK2:// Is this affected by BaseLV and SPL too??? [Rytech] + skillratio += -100 + 85000 + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_CRYSTAL_IMPACT: + skillratio += -100 + 250 + 1300 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CLIMAX)) { + if (sc->getSCE(SC_CLIMAX)->val1 == 3) + skillratio += skillratio * 50 / 100; + else if (sc->getSCE(SC_CLIMAX)->val1 == 4) + skillratio /= 2; + } + break; + case AG_CRYSTAL_IMPACT_ATK:// Said to deal the same damage as the main attack. + skillratio += -100 + 800 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_CLIMAX) && sc->getSCE(SC_CLIMAX)->val1 == 4) + skillratio += skillratio * 150 / 100; + break; + case AG_TORNADO_STORM: + skillratio += -100 + 100 + 760 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_FLORAL_FLARE_ROAD: + skillratio += -100 + 50 + 740 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_ASTRAL_STRIKE: + skillratio += -100 + 1800 * skill_lv + 10 * sstatus->spl; + if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DRAGON) + skillratio += 340 * skill_lv; + RE_LVL_DMOD(100); + break; + case AG_ASTRAL_STRIKE_ATK: + skillratio += -100 + 650 * skill_lv + 10 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_ROCK_DOWN: + skillratio += -100 + 1550 * skill_lv + 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_CLIMAX ) ){ + skillratio += 300 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case AG_STORM_CANNON: + skillratio += -100 + 1550 * skill_lv + 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_CLIMAX ) ){ + skillratio += 300 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case AG_CRIMSON_ARROW: + skillratio += -100 + 400 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_CRIMSON_ARROW_ATK: + skillratio += -100 + 750 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case AG_FROZEN_SLASH: + skillratio += -100 + 450 + 950 * skill_lv + 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_CLIMAX ) ){ + skillratio += 150 + 350 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case IG_JUDGEMENT_CROSS: + skillratio += -100 + 1950 * skill_lv + 10 * sstatus->spl; + if (tstatus->race == RC_PLANT || tstatus->race == RC_INSECT) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + if ((i = pc_checkskill_imperial_guard(sd, 3)) > 0) + skillratio += skillratio * i / 100; + break; + case IG_CROSS_RAIN: + if( sc && sc->getSCE( SC_HOLY_S ) ){ + skillratio += -100 + ( 650 + 15 * pc_checkskill( sd, IG_SPEAR_SWORD_M ) ) * skill_lv; + }else{ + skillratio += -100 + ( 450 + 10 * pc_checkskill( sd, IG_SPEAR_SWORD_M ) ) * skill_lv; + } + skillratio += 7 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case CD_ARBITRIUM: + skillratio += -100 + 1000 * skill_lv + 10 * sstatus->spl; + skillratio += 10 * pc_checkskill( sd, CD_FIDUS_ANIMUS ) * skill_lv; + RE_LVL_DMOD(100); + break; + case CD_ARBITRIUM_ATK: + skillratio += -100 + 1750 * skill_lv + 10 * sstatus->spl; + skillratio += 50 * pc_checkskill( sd, CD_FIDUS_ANIMUS ) * skill_lv; + RE_LVL_DMOD(100); + break; + case CD_PNEUMATICUS_PROCELLA: + skillratio += -100 + 150 + 2100 * skill_lv + 10 * sstatus->spl; + skillratio += 3 * pc_checkskill( sd, CD_FIDUS_ANIMUS ); + if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON) { + skillratio += 50 + 150 * skill_lv; + skillratio += 2 * pc_checkskill( sd, CD_FIDUS_ANIMUS ); + } + RE_LVL_DMOD(100); + break; + case CD_FRAMEN: + skillratio += -100 + 1300 * skill_lv; + skillratio += 5 * pc_checkskill(sd,CD_FIDUS_ANIMUS) * skill_lv; + skillratio += 5 * sstatus->spl; + if (tstatus->race == RC_UNDEAD || tstatus->race == RC_DEMON) + skillratio += 50 * skill_lv; + RE_LVL_DMOD(100); + break; + case AG_DESTRUCTIVE_HURRICANE_CLIMAX:// Is this affected by BaseLV and SPL too??? [Rytech] + skillratio += -100 + 500 + 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case ABC_ABYSS_STRIKE: + skillratio += -100 + 2650 * skill_lv; + skillratio += 10 * sstatus->spl; + if (tstatus->race == RC_DEMON || tstatus->race == RC_ANGEL) + skillratio += 200 * skill_lv; + RE_LVL_DMOD(100); + break; + case ABC_ABYSS_SQUARE: + skillratio += -100 + 750 * skill_lv; + skillratio += 40 * pc_checkskill( sd, ABC_MAGIC_SWORD_M ) * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case TR_METALIC_FURY: + skillratio += -100 + 3850 * skill_lv; + // !Todo: skill affected by SPL (without SC_SOUNDBLEND) as well? + if (tsc && tsc->getSCE(SC_SOUNDBLEND)) { + skillratio += 800 * skill_lv; + skillratio += 2 * pc_checkskill(sd, TR_STAGE_MANNER) * sstatus->spl; + } + RE_LVL_DMOD(100); + break; + case TR_SOUNDBLEND: + skillratio += -100 + 120 * skill_lv + 5 * sstatus->spl; + RE_LVL_DMOD(100); + if (sc && sc->getSCE(SC_MYSTIC_SYMPHONY)) { + skillratio += skillratio * 100 / 100; + + if (tstatus->race == RC_FISH || tstatus->race == RC_DEMIHUMAN) + skillratio += skillratio * 50 / 100; + } + break; + case EM_DIAMOND_STORM: + skillratio += -100 + 500 + 2400 * skill_lv; + skillratio += 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_SUMMON_ELEMENTAL_DILUVIO ) ){ + skillratio += 7300 + 200 * skill_lv; + skillratio += 5 * sstatus->spl; + } + + RE_LVL_DMOD(100); + break; + case EM_LIGHTNING_LAND: + skillratio += -100 + 700 + 1100 * skill_lv; + skillratio += 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_SUMMON_ELEMENTAL_PROCELLA ) ){ + skillratio += 200 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case EM_VENOM_SWAMP: + skillratio += -100 + 700 + 1100 * skill_lv; + skillratio += 5 * sstatus->spl; + + if( sc && sc->getSCE( SC_SUMMON_ELEMENTAL_SERPENS ) ){ + skillratio += 200 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case EM_CONFLAGRATION: + skillratio += -100 + 700 + 1100 * skill_lv; + skillratio += 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_SUMMON_ELEMENTAL_ARDOR ) ){ + skillratio += 200 * skill_lv; + } + + RE_LVL_DMOD(100); + break; + case EM_TERRA_DRIVE: + skillratio += -100 + 500 + 2400 * skill_lv; + skillratio += 5 * sstatus->spl; + + if( sc != nullptr && sc->getSCE( SC_SUMMON_ELEMENTAL_TERREMOTUS ) ){ + skillratio += 7300 + 200 * skill_lv; + skillratio += 5 * sstatus->spl; + } + + RE_LVL_DMOD(100); + break; + case ABC_FROM_THE_ABYSS_ATK: + skillratio += -100 + 150 + 650 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case EM_ELEMENTAL_BUSTER_FIRE: + case EM_ELEMENTAL_BUSTER_WATER: + case EM_ELEMENTAL_BUSTER_WIND: + case EM_ELEMENTAL_BUSTER_GROUND: + case EM_ELEMENTAL_BUSTER_POISON: + skillratio += -100 + 550 + 2650 * skill_lv; + skillratio += 10 * sstatus->spl; + if (tstatus->race == RC_FORMLESS || tstatus->race == RC_DRAGON) + skillratio += 150 * skill_lv; + RE_LVL_DMOD(100); + break; + case EM_EL_FLAMEROCK: + skillratio += -100 + 2400; + if (ed) + skillratio += skillratio * status_get_lv(&ed->master->bl) / 100; + break; + case EM_EL_AGE_OF_ICE: + skillratio += -100 + 3700; + if (ed) + skillratio += skillratio * status_get_lv(&ed->master->bl) / 100; + break; + case EM_EL_STORM_WIND: + skillratio += -100 + 2600; + if (ed) + skillratio += skillratio * status_get_lv(&ed->master->bl) / 100; + break; + case EM_EL_AVALANCHE: + skillratio += -100 + 450; + if (ed) + skillratio += skillratio * status_get_lv(&ed->master->bl) / 100; + break; + case EM_EL_DEADLY_POISON: + skillratio += -100 + 700; + if (ed) + skillratio += skillratio * status_get_lv(&ed->master->bl) / 100; + break; + case NPC_RAINOFMETEOR: + skillratio += 350; // unknown ratio + break; + case HN_NAPALM_VULCAN_STRIKE: + skillratio += -100 + 350 + 650 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 4 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case HN_JUPITEL_THUNDER_STORM: + skillratio += -100 + 1800 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 3 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case HN_HELLS_DRIVE: + skillratio += -100 + 1700 + 900 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 4 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case HN_GROUND_GRAVITATION: + if (mflag & SKILL_ALTDMG_FLAG) { + skillratio += -100 + 3000 + 1500 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 4 * skill_lv; + ad.div_ = -2; + } else { + skillratio += -100 + 800 + 700 * skill_lv; + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 2 * skill_lv; + } + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case HN_JACK_FROST_NOVA: + if (mflag & SKILL_ALTDMG_FLAG) { + skillratio += -100 + 200 * skill_lv; + } else { + skillratio += -100 + 400 + 500 * skill_lv; + } + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 3 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + case HN_METEOR_STORM_BUSTER: + if (mflag & SKILL_ALTDMG_FLAG) { + skillratio += -100 + 300 + 160 * skill_lv * 2; + ad.div_ = -3; + } else { + skillratio += -100 + 450 + 160 * skill_lv; + } + skillratio += pc_checkskill(sd, HN_SELFSTUDY_SOCERY) * 5 * skill_lv; + skillratio += 5 * sstatus->spl; + RE_LVL_DMOD(100); + break; + } + + if (sc) {// Insignia's increases the damage of offensive magic by a fixed percentage depending on the element. + if ((sc->getSCE(SC_FIRE_INSIGNIA) && sc->getSCE(SC_FIRE_INSIGNIA)->val1 == 3 && s_ele == ELE_FIRE) || + (sc->getSCE(SC_WATER_INSIGNIA) && sc->getSCE(SC_WATER_INSIGNIA)->val1 == 3 && s_ele == ELE_WATER) || + (sc->getSCE(SC_WIND_INSIGNIA) && sc->getSCE(SC_WIND_INSIGNIA)->val1 == 3 && s_ele == ELE_WIND) || + (sc->getSCE(SC_EARTH_INSIGNIA) && sc->getSCE(SC_EARTH_INSIGNIA)->val1 == 3 && s_ele == ELE_EARTH)) + skillratio += 25; + } +#ifdef RENEWAL + // S.MATK needs to be applied before the skill ratio to prevent rounding issues + if (sd && sstatus->smatk > 0) + ad.damage += ad.damage * sstatus->smatk / 100; +#endif + MATK_RATE(skillratio); + + //Constant/misc additions from skills + if (skill_id == WZ_FIREPILLAR) + MATK_ADD(100 + 50 * skill_lv); + break; + } + } +#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_ele & ( 1 << ELE_ALL ) || + sd->bonus.ignore_mdef_race & ( 1 << tstatus->race ) || sd->bonus.ignore_mdef_race & ( 1 << RC_ALL ) || + sd->bonus.ignore_mdef_class & ( 1 << tstatus->class_ ) || sd->bonus.ignore_mdef_class & ( 1 << CLASS_ALL ) + )) + flag.imdef = 1; + } + + if (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id))) + ad.damage -= (int64)ad.damage*i/100; + +#ifdef RENEWAL + // MRes reduces magical damage by a percentage and + // is calculated before MDEF and other reductions. + // TODO: MRes formula probably should be: (2000+x)/(2000+5x), but with the reduction rounded down + if (ad.damage && tstatus->mres > 0) { + short mres = tstatus->mres; + short ignore_mres = 0;// Value used as percentage. + + if (sd) + ignore_mres += sd->indexed_bonus.ignore_mres_by_race[tstatus->race] + sd->indexed_bonus.ignore_mres_by_race[RC_ALL]; + + // Attacker status's that pierce MRes. + if (sc && sc->getSCE(SC_A_VITA)) + ignore_mres += sc->getSCE(SC_A_VITA)->val2; + + ignore_mres = min(ignore_mres, battle_config.max_res_mres_ignored); + + if (ignore_mres > 0) + mres -= mres * ignore_mres / 100; + + // Apply damage reduction. + ad.damage = ad.damage * (5000 + mres) / (5000 + 10 * mres); + } +#endif + + if(!flag.imdef){ + defType mdef = tstatus->mdef; + int mdef2= tstatus->mdef2; + + if (sc && sc->getSCE(SC_EXPIATIO)) { + i = 5 * sc->getSCE(SC_EXPIATIO)->val1; // 5% per level + + i = min(i, 100); //cap it to 100 for 5 mdef min + mdef -= mdef * i / 100; + //mdef2 -= mdef2 * i / 100; + } + + if(sd) { + i = sd->indexed_bonus.ignore_mdef_by_race[tstatus->race] + sd->indexed_bonus.ignore_mdef_by_race[RC_ALL]; + i += sd->indexed_bonus.ignore_mdef_by_class[tstatus->class_] + sd->indexed_bonus.ignore_mdef_by_class[CLASS_ALL]; + + std::vector race2 = status_get_race2(target); + + for (const auto &raceit : race2) + i += sd->indexed_bonus.ignore_mdef_by_race2[raceit]; + 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 + */ + if (mdef < 0) + mdef = 0; // Negative eMDEF is treated as 0 on official + + 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 + } + + //Apply the physical part of the skill's damage. [Skotlex] + switch (skill_id) { + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: { + // Pre-re ATK = Take atk, apply def reduction and add refine bonus + // Final Damage = (ATK+MATK)*RATIO + // Renewal ATK = Take total atk + // Final Damage = ((ATK+MATK)/2)*RATIO - (tDEF + tMDEF) + // No need to go through the whole physical damage code + struct Damage wd = initialize_weapon_data(src, target, skill_id, skill_lv, mflag); + battle_calc_skill_base_damage(&wd, src, target, skill_id, skill_lv); + // Calculate ATK +#ifdef RENEWAL + if (sd) + wd.damage = wd.statusAtk + wd.weaponAtk + wd.equipAtk + wd.percentAtk; +#else + battle_calc_defense_reduction(&wd, src, target, skill_id, skill_lv); + if (sd) { + wd.damage += sstatus->rhw.atk2; + } +#endif + // Combine ATK and MATK +#ifdef RENEWAL + ad.damage = (wd.damage + ad.damage) / 2; +#else + ad.damage = std::max((int64)1, wd.damage + ad.damage); +#endif + // Ratio + skillratio += 40 * skill_lv; + MATK_RATE(skillratio); +#ifdef RENEWAL + // Total defense reduction (renewal only) + battle_calc_defense_reduction(&ad, src, target, skill_id, skill_lv); + ad.damage -= (tstatus->mdef + tstatus->mdef2); +#endif + } + break; + } + + if(ad.damage<1) + ad.damage=1; + else if(sc) { //only applies when hit + switch(skill_id) { + case MG_LIGHTNINGBOLT: + case MG_THUNDERSTORM: + if(sc->getSCE(SC_GUST_OPTION)) + ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30; + break; + case MG_FIREBOLT: + case MG_FIREWALL: + if(sc->getSCE(SC_PYROTECHNIC_OPTION)) + ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30; + break; + case MG_COLDBOLT: + case MG_FROSTDIVER: + if(sc->getSCE(SC_AQUAPLAY_OPTION)) + ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30; + break; + case WZ_EARTHSPIKE: + case WZ_HEAVENDRIVE: + if(sc->getSCE(SC_PETROLOGY_OPTION)) + ad.damage += (6 + sstatus->int_ / 4) + max(sstatus->dex - 10, 0) / 30; + break; + } + } + + if (!nk[NK_IGNOREELEMENT]) + ad.damage = battle_attr_fix(src, target, ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv); + +#ifndef RENEWAL + ad.damage += battle_calc_cardfix(BF_MAGIC, src, target, nk, s_ele, 0, ad.damage, 0, ad.flag); +#endif + + switch (skill_id) { + case CR_GRANDCROSS: + case NPC_GRANDDARKNESS: + if (src == target) { + // Grand Cross on self first applies attr_fix, then cardfix and finally halves the damage + if (src->type == BL_PC) + ad.damage = ad.damage / 2; + else + ad.damage = 0; + } + else + // Grand Cross on target applies attr_fix, then cardfix and then attr_fix a second time + ad.damage = battle_attr_fix(src, target, ad.damage, s_ele, tstatus->def_ele, tstatus->ele_lv); + break; + } + + } //Hint: Against plants damage will still be 1 at this point + + //Apply DAMAGE_DIV_FIX and check for min damage + battle_apply_div_fix(&ad, skill_id); + + struct map_data *mapdata = map_getmapdata(target->m); + + ad.damage = battle_calc_damage(src,target,&ad,ad.damage,skill_id,skill_lv); + if (mapdata_flag_gvg2(mapdata)) + ad.damage = battle_calc_gvg_damage(src,target,ad.damage,skill_id,ad.flag); + else if (mapdata->getMapFlag(MF_BATTLEGROUND)) + ad.damage = battle_calc_bg_damage(src,target,ad.damage,skill_id,ad.flag); + + // Skill damage adjustment + if ((skill_damage = battle_skill_damage(src,target,skill_id)) != 0) + MATK_ADDRATE(skill_damage); + + battle_absorb_damage(target, &ad); + + //battle_do_reflect(BF_MAGIC,&ad, src, target, skill_id, skill_lv); //WIP [lighta] Magic skill has own handler at skill_attack + return ad; +} + +/*========================================== + * Calculate "misc"-type attacks and skills + *------------------------------------------ + * Credits: + * Original coder Skotlex + * Refined and optimized by helvetica + */ +struct Damage battle_calc_misc_attack(struct block_list *src,struct block_list *target,uint16 skill_id,uint16 skill_lv,int mflag) +{ + int skill_damage = 0; + short i, s_ele; + + map_session_data *sd, *tsd; + struct Damage md; //DO NOT CONFUSE with md of mob_data! + status_change *ssc = status_get_sc(src); + + memset(&md,0,sizeof(md)); + + if (src == nullptr || target == nullptr) { + nullpo_info(NLP_MARK); + return md; + } + + status_data* sstatus = status_get_status_data(*src); + status_data* tstatus = status_get_status_data(*target); + + //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; + md.miscflag = mflag; + + std::shared_ptr skill = skill_db.find(skill_id); + std::bitset nk; + + if (skill) + nk = skill->nk; + + 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 = battle_get_misc_element(src, target, skill_id, skill_lv, mflag); + + //Skill Range Criteria + md.flag |= battle_range_type(src, target, skill_id, skill_lv); + + switch (skill_id) { + case TF_THROWSTONE: + if (sd) + md.damage = 50; + else + md.damage = 30; + md.flag |= BF_WEAPON; + break; + case NPC_KILLING_AURA: + md.damage = 10000; + break; +#ifdef RENEWAL + case HT_LANDMINE: + case MA_LANDMINE: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + md.damage = (int64)(skill_lv * sstatus->dex * (3.0 + (float)status_get_lv(src) / 100.0) * (1.0 + (float)sstatus->int_ / 35.0)); + md.damage += md.damage * (rnd()%20 - 10) / 100; + md.damage += (sd ? pc_checkskill(sd,RA_RESEARCHTRAP) * 40 : 0); + break; +#else + case HT_LANDMINE: + case MA_LANDMINE: + md.damage = static_cast(skill_lv * (sstatus->dex + 75.0) * (100.0 + sstatus->int_) / 100.0); + break; + case HT_BLASTMINE: + md.damage = static_cast(skill_lv * (sstatus->dex / 2.0 + 50.0) * (100.0 + sstatus->int_) / 100.0); + break; + case HT_CLAYMORETRAP: + md.damage = static_cast(skill_lv * (sstatus->dex / 2.0 + 75.0) * (100.0 + sstatus->int_) / 100.0); + break; +#endif + case HT_BLITZBEAT: + case SN_FALCONASSAULT: + { + uint16 skill; + + //Blitz-beat Damage + if(!sd || !(skill = pc_checkskill(sd,HT_STEELCROW))) + skill = 0; +#ifdef RENEWAL + md.damage = (sstatus->dex / 10 + sstatus->agi / 2 + skill * 3 + 40) * 2; + RE_LVL_MDMOD(100); +#else + md.damage = (sstatus->dex / 10 + sstatus->int_ / 2 + skill * 3 + 40) * 2; + if(mflag > 1) //Autocasted Blitz + nk.set(NK_SPLASHSPLIT); +#endif + if (skill_id == SN_FALCONASSAULT) { + //Div fix of Blitzbeat + DAMAGE_DIV_FIX2(md.damage, skill_get_num(HT_BLITZBEAT, 5)); + //Falcon Assault Modifier + md.damage = md.damage * (150 + 70 * skill_lv) / 100; + } + } + break; +#ifndef RENEWAL + case BA_DISSONANCE: + md.damage = 30 + skill_lv * 10; + if (sd) + md.damage += 3 * pc_checkskill(sd,BA_MUSICALLESSON); + break; +#endif + case NPC_SELFDESTRUCTION: + md.damage = sstatus->hp; + break; + case NPC_SMOKING: + md.damage = 3; + break; + case NPC_EVILLAND: + md.damage = skill_calc_heal(src,target,skill_id,skill_lv,false); + break; +#ifndef RENEWAL + case ASC_BREAKER: + md.damage = 500 + rnd()%500 + 5 * skill_lv * sstatus->int_; + nk.set(NK_IGNOREFLEE); + nk.set(NK_IGNOREELEMENT); //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 PA_PRESSURE: + md.damage = 500 + 300 * skill_lv; + break; +#endif + case PA_GOSPEL: + if (mflag > 0) + md.damage = (rnd() % 4000) + 1500; + else { + md.damage = (rnd() % 5000) + 3000; +#ifdef RENEWAL + md.damage -= (int64)status_get_def(target); +#else + md.damage -= (md.damage * (int64)status_get_def(target)) / 100; +#endif + md.damage -= tstatus->def2; + if (md.damage < 0) + md.damage = 0; + } + break; + case GN_FIRE_EXPANSION_ACID: +#ifdef RENEWAL + // Official Renewal formula [helvetica] + // damage = 7 * ((atk + matk)/skill level) * (target vit/100) + // skill is a "forced neutral" type skill, it benefits from weapon element but final damage + // is considered "neutral" for purposes of resistances + { + struct Damage atk = battle_calc_weapon_attack(src, target, skill_id, skill_lv, 0); + struct Damage matk = battle_calc_magic_attack(src, target, skill_id, skill_lv, 0); + md.damage = 7 * ((atk.damage/skill_lv + matk.damage/skill_lv) * tstatus->vit / 100 ); + + // AD benefits from endow/element but damage is forced back to neutral + md.damage = battle_attr_fix(src, target, md.damage, ELE_NEUTRAL, tstatus->def_ele, tstatus->ele_lv); + } + // Fall through +#else + case CR_ACIDDEMONSTRATION: + 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 /= 2; +#endif + break; + case NJ_ZENYNAGE: + md.damage = skill_get_zeny( skill_id, skill_lv ); + + if( md.damage == 0 ){ + md.damage = 2; + } + + md.damage += rnd_value( static_cast( 0 ), md.damage ); + + // Specific to Boss Class + if( status_get_class_( target ) == CLASS_BOSS ){ + md.damage /= 3; + } + + if( tsd != nullptr ){ + md.damage /= 2; + } + break; + case KO_MUCHANAGE: + md.damage = skill_get_zeny( skill_id, skill_lv ); + + if( md.damage == 0 ){ + md.damage = 10; + } + + md.damage = rnd_value( md.damage / 2, md.damage ); + + if( pc_checkskill( sd, NJ_TOBIDOUGU ) == 0 ){ + md.damage /= 2; + } + + // Specific to Boss Class + if( status_get_class_( target ) == CLASS_BOSS ){ + md.damage /= 2; + } + break; +#ifdef RENEWAL + case NJ_ISSEN: + // Official Renewal formula [helvetica] + // base damage = currenthp + ((atk * currenthp * skill level) / maxhp) + // final damage = base damage + ((mirror image count + 1) / 5 * base damage) - (edef + sdef) + // modified def formula + { + short totaldef; + struct Damage atk = battle_calc_weapon_attack(src, target, skill_id, skill_lv, 0); + status_change *sc = status_get_sc(src); + + md.damage = (int64)sstatus->hp + (atk.damage * (int64)sstatus->hp * skill_lv) / (int64)sstatus->max_hp; + + if (sc && sc->getSCE(SC_BUNSINJYUTSU) && (i = sc->getSCE(SC_BUNSINJYUTSU)->val2) > 0) { // mirror image bonus only occurs if active + md.div_ = -(i + 2); // mirror image count + 2 + md.damage += (md.damage * (((i + 1) * 10) / 5)) / 10; + } + // modified def reduction, final damage = base damage - (edef + sdef) + totaldef = tstatus->def2 + (short)status_get_def(target); + md.damage -= totaldef; + md.flag |= BF_WEAPON; + } + break; +#endif + 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 RA_CLUSTERBOMB: + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + md.damage = skill_lv * status_get_dex(src) + status_get_int(src) * 5 ; + RE_LVL_TMDMOD(); + if(sd) { + int researchskill_lv = pc_checkskill(sd,RA_RESEARCHTRAP); + if(researchskill_lv) + md.damage = md.damage * 20 * researchskill_lv / (skill_id == RA_CLUSTERBOMB ? 50 : 100); + else + md.damage = 0; + } else + md.damage = md.damage * 200 / (skill_id == RA_CLUSTERBOMB ? 50 : 100); + nk.set(NK_IGNOREELEMENT); + nk.set(NK_IGNOREFLEE); + nk.set(NK_IGNOREDEFCARD); + break; + case NC_MAGMA_ERUPTION_DOTDAMAGE: // 'Eruption' damage + md.damage = 800 + 200 * skill_lv; + break; + case NPC_MAGMA_ERUPTION_DOTDAMAGE: + md.damage = 1000 * skill_lv; + break; + case GN_THORNS_TRAP: + md.damage = 100 + 200 * skill_lv + status_get_int(src); + break; + case RL_B_TRAP: + // kRO 2014-02-12: Damage: Caster's DEX, Target's current HP, Skill Level + md.damage = status_get_dex(src) * 10 + (skill_lv * 3 * status_get_hp(target)) / 100; + if (status_bl_has_mode(target, MD_STATUSIMMUNE)) + md.damage /= 10; + break; + case NPC_WIDESUCK: + md.damage = tstatus->max_hp * 15 / 100; + break; + case SU_SV_ROOTTWIST_ATK: + md.damage = 100; + break; + case SP_SOULEXPLOSION: + md.damage = tstatus->hp * (20 + 10 * skill_lv) / 100; + break; + case SJ_NOVAEXPLOSING: + // (Base ATK + Weapon ATK) * Ratio + md.damage = (sstatus->batk + sstatus->rhw.atk) * (200 + 100 * skill_lv) / 100; + + // Additional Damage + md.damage += sstatus->max_hp / (6 - min(5, skill_lv)) + status_get_max_sp(src) * (2 * skill_lv); + break; + case NPC_CANE_OF_EVIL_EYE: + md.damage = 15000; + 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)); + } + + if (!nk[NK_IGNOREFLEE]) { + 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 = unit_counttargeted(target); //256 max targets should be a sane max + + 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 //assume 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 shown anymore 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 (tsd && (i = pc_sub_skillatk_bonus(tsd, skill_id))) + md.damage -= (int64)md.damage*i/100; + + if(!nk[NK_IGNOREELEMENT]) + md.damage=battle_attr_fix(src, target, md.damage, s_ele, tstatus->def_ele, tstatus->ele_lv); + + //Plant damage + if(md.damage < 0) + md.damage = 0; + else if(md.damage && is_infinite_defense(target, md.flag)) { + md.damage = 1; + } + + //Apply DAMAGE_DIV_FIX and check for min damage + battle_apply_div_fix(&md, skill_id); + + switch(skill_id) { + case RA_FIRINGTRAP: + case RA_ICEBOUNDTRAP: + if (md.damage == 1) + break; + [[fallthrough]]; + case RA_CLUSTERBOMB: + { + struct Damage 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, static_cast( md.damage ), LOG_TYPE_CONSUME ); + } + break; + } + + struct map_data *mapdata = map_getmapdata(target->m); + + md.damage = battle_calc_damage(src,target,&md,md.damage,skill_id,skill_lv); + if(mapdata_flag_gvg2(mapdata)) + md.damage = battle_calc_gvg_damage(src,target,md.damage,skill_id,md.flag); + else if(mapdata->getMapFlag(MF_BATTLEGROUND)) + md.damage = battle_calc_bg_damage(src,target,md.damage,skill_id,md.flag); + + // Skill damage adjustment + if ((skill_damage = battle_skill_damage(src,target,skill_id)) != 0) + md.damage += (int64)md.damage * skill_damage / 100; + + battle_absorb_damage(target, &md); + + battle_do_reflect(BF_MISC,&md, src, target, skill_id, skill_lv); //WIP [lighta] + + return md; +} + +/** + * Calculate vanish damage on a target + * @param sd: Player with vanish item + * @param target: Target to vanish HP/SP + * @param flag: Damage struct battle flag + */ +void battle_vanish_damage(map_session_data *sd, struct block_list *target, int flag) +{ + nullpo_retv(sd); + nullpo_retv(target); + + // bHPVanishRate + int16 vanish_hp = 0; + if (!sd->hp_vanish.empty()) { + for (auto &it : sd->hp_vanish) { + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + if (it.rate && (it.rate >= 1000 || rnd() % 1000 < it.rate)) + vanish_hp += it.per; + } + } + + // bSPVanishRate + int16 vanish_sp = 0; + if (!sd->sp_vanish.empty()) { + for (auto &it : sd->sp_vanish) { + if (!(((it.flag)&flag)&BF_WEAPONMASK && + ((it.flag)&flag)&BF_RANGEMASK && + ((it.flag)&flag)&BF_SKILLMASK)) + continue; + if (it.rate && (it.rate >= 1000 || rnd() % 1000 < it.rate)) + vanish_sp += it.per; + } + } + + if (vanish_hp > 0 || vanish_sp > 0) + status_percent_damage(&sd->bl, target, -vanish_hp, -vanish_sp, false); // Damage HP/SP applied once +} + +/*========================================== + * Battle main entry, from skill_attack + *------------------------------------------ + * Credits: + * Original coder unknown + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +struct Damage battle_calc_attack(int attack_type,struct block_list *bl,struct block_list *target,uint16 skill_id,uint16 skill_lv,int flag) +{ + struct Damage d; + + switch(attack_type) { + case BF_WEAPON: d = battle_calc_weapon_attack(bl,target,skill_id,skill_lv,flag); break; + case BF_MAGIC: d = battle_calc_magic_attack(bl,target,skill_id,skill_lv,flag); break; + case BF_MISC: d = battle_calc_misc_attack(bl,target,skill_id,skill_lv,flag); break; + default: + ShowError("battle_calc_attack: unknown attack type! %d (skill_id=%d, skill_lv=%d)\n", attack_type, skill_id, skill_lv); + 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; + if(bl->type == BL_PC) + d.div_ = 1; + + status_change *tsc = status_get_sc(target); + + // Weapon Blocking has the ability to trigger on ATK_MISS as well. + if (tsc != nullptr && tsc->getSCE(SC_WEAPONBLOCKING)) { + status_change_entry *tsce = tsc->getSCE(SC_WEAPONBLOCKING); + + if (attack_type == BF_WEAPON && rnd() % 100 < tsce->val2) { + clif_skill_nodamage(target, bl, GC_WEAPONBLOCKING, tsce->val1, 1); + sc_start(bl, target, SC_WEAPONBLOCK_ON, 100, bl->id, skill_get_time2(GC_WEAPONBLOCKING, tsce->val1)); + } + } + } + else // Some skills like Weaponry Research will cause damage even if attack is dodged + d.dmg_lv = ATK_DEF; + + map_session_data *sd = BL_CAST(BL_PC, bl); + + if (sd && d.damage + d.damage2 > 1) + battle_vanish_damage(sd, target, d.flag); + + return d; +} + +/*========================================== + * Final damage return function + *------------------------------------------ + * Credits: + * Original coder unknown + * Initial refactoring by Baalberith + * Refined and optimized by helvetica + */ +int64 battle_calc_return_damage(struct block_list* tbl, struct block_list *src, int64 *dmg, int flag, uint16 skill_id, bool status_reflect){ + status_change *tsc = status_get_sc(tbl); + + if (tsc) { // These statuses do not reflect any damage (off the target) + if (tsc->getSCE(SC_WHITEIMPRISON) || tsc->getSCE(SC_DARKCROW) || tsc->getSCE(SC_KYOMU)) + return 0; + } + + status_change *sc = status_get_sc(src); + + if (sc) { + if (skill_id == GN_HELLS_PLANT_ATK && sc->getSCE(SC_HELLS_PLANT)) + return 0; + } + + map_session_data *tsd = BL_CAST(BL_PC, tbl); + int64 rdamage = 0, damage = *dmg; + + if (flag & BF_SHORT) {//Bounces back part of the damage. + if ( (skill_get_inf2(skill_id, INF2_ISTRAP) || !status_reflect) && tsd && tsd->bonus.short_weapon_damage_return ) { + rdamage += damage * tsd->bonus.short_weapon_damage_return / 100; + } else if( status_reflect && tsc && tsc->count ) { + if( tsc->getSCE(SC_REFLECTSHIELD) ) { + status_change_entry *sce_d; + block_list *d_bl; + + if( (sce_d = tsc->getSCE(SC_DEVOTION)) && (d_bl = map_id2bl(sce_d->val1)) && + ((d_bl->type == BL_MER && ((TBL_MER*)d_bl)->master && ((TBL_MER*)d_bl)->master->bl.id == tbl->id) || + (d_bl->type == BL_PC && ((TBL_PC*)d_bl)->devotion[sce_d->val2] == tbl->id)) ) + { //Don't reflect non-skill attack if has SC_REFLECTSHIELD from Devotion bonus inheritance + if( (!skill_id && battle_config.devotion_rdamage_skill_only && tsc->getSCE(SC_REFLECTSHIELD)->val4) || + !check_distance_bl(tbl,d_bl,sce_d->val3) ) + return 0; + } + } + if ( tsc->getSCE(SC_REFLECTSHIELD) && skill_id != WS_CARTTERMINATION && skill_id != NPC_MAXPAIN_ATK ) { + // Don't reflect non-skill attack if has SC_REFLECTSHIELD from Devotion bonus inheritance + if (!skill_id && battle_config.devotion_rdamage_skill_only && tsc->getSCE(SC_REFLECTSHIELD)->val4) + rdamage = 0; + else { + rdamage += damage * tsc->getSCE(SC_REFLECTSHIELD)->val2 / 100; + } + } + + if (tsc->getSCE(SC_DEATHBOUND) && skill_id != WS_CARTTERMINATION && skill_id != GN_HELLS_PLANT_ATK && !status_bl_has_mode(src,MD_STATUSIMMUNE)) { + if (distance_bl(src,tbl) <= 0 || !map_check_dir(map_calc_dir(tbl,src->x,src->y), unit_getdir(tbl))) { + int64 rd1 = i64min(damage, status_get_max_hp(tbl)) * tsc->getSCE(SC_DEATHBOUND)->val2 / 100; // Amplify damage. + + *dmg = rd1 * 30 / 100; // Received damage = 30% of amplified damage. + clif_skill_damage(src, tbl, gettick(), status_get_amotion(src), 0, -30000, 1, RK_DEATHBOUND, tsc->getSCE(SC_DEATHBOUND)->val1, DMG_SINGLE); + skill_blown(tbl, src, skill_get_blewcount(RK_DEATHBOUND, tsc->getSCE(SC_DEATHBOUND)->val1), unit_getdir(src), BLOWN_NONE); + status_change_end(tbl, SC_DEATHBOUND); + rdamage += rd1 * 70 / 100; // Target receives 70% of the amplified damage. [Rytech] + } + } + } + } else { + if (!status_reflect && tsd && tsd->bonus.long_weapon_damage_return) { + rdamage += damage * tsd->bonus.long_weapon_damage_return / 100; + } + } + + // Config damage adjustment + map_data *mapdata = map_getmapdata(src->m); + + if (mapdata_flag_gvg2(mapdata)) + rdamage = battle_calc_gvg_damage(src, tbl, rdamage, skill_id, flag); + else if (mapdata->getMapFlag(MF_BATTLEGROUND)) + rdamage = battle_calc_bg_damage(src, tbl, rdamage, skill_id, flag); + else if (mapdata->getMapFlag(MF_PVP)) + rdamage = battle_calc_pk_damage(*src, *tbl, rdamage, skill_id, flag); + + // Skill damage adjustment + int skill_damage = battle_skill_damage(src, tbl, skill_id); + + if (skill_damage != 0) { + rdamage += rdamage * skill_damage / 100; + } + + int64 reduce = 0; + map_session_data* sd = BL_CAST(BL_PC, src); + + if (sd && sd->bonus.reduce_damage_return != 0) { + reduce += (sd->bonus.reduce_damage_return); + } + + if (sc) { + if (status_reflect && sc->getSCE(SC_REFLECTDAMAGE)) { + reduce += sc->getSCE(SC_REFLECTDAMAGE)->val2; + } + if (sc->getSCE(SC_VENOMBLEED) && sc->getSCE(SC_VENOMBLEED)->val3 == 0) { + reduce += sc->getSCE(SC_VENOMBLEED)->val2; + } + if (sc->getSCE(SC_REF_T_POTION)) + reduce += 100; + } + + if (rdamage > 0) { + rdamage -= rdamage * i64min(100, reduce) / 100; + rdamage = i64max(rdamage, 1); + } + + if (rdamage == 0) + return 0; // No reflecting damage calculated. + else + return cap_value(rdamage, 1, status_get_max_hp(tbl)); +} + +/** Check for Coma damage + * @param sd: Source player + * @param bl: Target + * @param attack_type: Attack type + * @return True if Coma applies, false if Coma does not apply + */ +bool battle_check_coma(map_session_data& sd, struct block_list& target, e_battle_flag attack_type) +{ + status_data* tstatus = status_get_status_data(target); + mob_data* dstmd = BL_CAST(BL_MOB, &target); + + // Coma + if (sd.special_state.bonus_coma && (!dstmd || (!util::vector_exists(status_get_race2(&dstmd->bl), RC2_GVG) && status_get_class(&dstmd->bl) != CLASS_BATTLEFIELD))) { + int rate = 0; + rate += sd.indexed_bonus.coma_class[tstatus->class_] + sd.indexed_bonus.coma_class[CLASS_ALL]; + if(!status_bl_has_mode(&target, MD_STATUSIMMUNE)) + rate += sd.indexed_bonus.coma_race[tstatus->race] + sd.indexed_bonus.coma_race[RC_ALL]; + if (attack_type&BF_WEAPON) { + rate += sd.indexed_bonus.weapon_coma_class[tstatus->class_] + sd.indexed_bonus.weapon_coma_class[CLASS_ALL]; + if (!status_bl_has_mode(&target, MD_STATUSIMMUNE)) { + rate += sd.indexed_bonus.weapon_coma_ele[tstatus->def_ele] + sd.indexed_bonus.weapon_coma_ele[ELE_ALL]; + rate += sd.indexed_bonus.weapon_coma_race[tstatus->race] + sd.indexed_bonus.weapon_coma_race[RC_ALL]; + } + } + if (rate > 0 && rnd_chance(rate, 10000)) + return true; + } + return false; +} + +/** + * Calculate Vellum damage on a target + * @param sd: Player with vanish item + * @param target: Target to vanish HP/SP + * @param wd: Damage struct reference + * @return True on damage done or false if not + */ +bool battle_vellum_damage(map_session_data *sd, struct block_list *target, struct Damage *wd) +{ + nullpo_retr(false, sd); + nullpo_retr(false, target); + nullpo_retr(false, wd); + + status_data* tstatus = status_get_status_data(*target); + // bHPVanishRaceRate + int16 vellum_rate_hp = cap_value(sd->hp_vanish_race[tstatus->race].rate + sd->hp_vanish_race[RC_ALL].rate, 0, INT16_MAX); + int8 vellum_hp = cap_value(sd->hp_vanish_race[tstatus->race].per + sd->hp_vanish_race[RC_ALL].per, INT8_MIN, INT8_MAX); + // bSPVanishRaceRate + int16 vellum_rate_sp = cap_value(sd->sp_vanish_race[tstatus->race].rate + sd->sp_vanish_race[RC_ALL].rate, 0, INT16_MAX); + int8 vellum_sp = cap_value(sd->sp_vanish_race[tstatus->race].per + sd->sp_vanish_race[RC_ALL].per, INT8_MIN, INT8_MAX); + + // The HP and SP damage bonus from these items don't stack because of the special damage display for SP. + // Vellum damage overrides any other damage done as well. + if (vellum_hp && vellum_rate_hp && (vellum_rate_hp >= 1000 || rnd() % 1000 < vellum_rate_hp)) { + wd->damage = apply_rate(tstatus->max_hp, vellum_hp); + wd->damage2 = 0; + } else if (vellum_sp && vellum_rate_sp && (vellum_rate_sp >= 1000 || rnd() % 1000 < vellum_rate_sp)) { + wd->damage = apply_rate(tstatus->max_sp, vellum_sp); + wd->damage2 = 0; + wd->isspdamage = true; + } else + return false; + + return true; +} + +/*=========================================== + * Perform battle drain effects (HP/SP loss) + *-------------------------------------------*/ +void battle_drain(map_session_data *sd, struct block_list *tbl, int64 rdamage, int64 ldamage, int race, int class_) +{ + struct weapon_data *wd; + int64 *damage; + int thp = 0, // HP gained + tsp = 0, // SP gained + //rhp = 0, // HP reduced from target + //rsp = 0, // SP reduced from target + hp = 0, sp = 0; + + if (!CHK_RACE(race) && !CHK_CLASS(class_)) + return; + + for (int 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; + + if (i == 1 || i == 3) { + hp = wd->hp_drain_class[class_] + wd->hp_drain_class[CLASS_ALL]; + hp += battle_calc_drain(*damage, wd->hp_drain_rate.rate, wd->hp_drain_rate.per); + + sp = wd->sp_drain_class[class_] + wd->sp_drain_class[CLASS_ALL]; + sp += battle_calc_drain(*damage, wd->sp_drain_rate.rate, wd->sp_drain_rate.per); + + if( hp ) { + //rhp += hp; + thp += hp; + } + + if( sp ) { + //rsp += sp; + tsp += sp; + } + } else { + hp = wd->hp_drain_race[race] + wd->hp_drain_race[RC_ALL]; + sp = wd->sp_drain_race[race] + wd->sp_drain_race[RC_ALL]; + + if( hp ) { + //rhp += hp; + thp += hp; + } + + if( sp ) { + //rsp += sp; + tsp += sp; + } + } + } + + 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. + *------------------------------------------- + * Credits: + * Original coder pakpil + */ +int battle_damage_area(struct block_list *bl, va_list ap) { + t_tick tick; + int64 damage; + int amotion, dmotion; + struct block_list *src; + + nullpo_ret(bl); + + tick = va_arg(ap, t_tick); + src = va_arg(ap,struct block_list *); + amotion = va_arg(ap,int); + dmotion = va_arg(ap,int); + damage = va_arg(ap,int); + + if (status_bl_has_mode(bl, MD_SKILLIMMUNE) || status_get_class(bl) == 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), status_get_class_(bl)); + if( amotion ) + battle_delay_damage(tick, amotion,src,bl,0,CR_REFLECTSHIELD,0,damage,ATK_DEF,0,true,false); + else + battle_fix_damage(src,bl,damage,0,LG_REFLECTDAMAGE); + clif_damage(*bl,*bl,tick,amotion,dmotion,damage,1,DMG_ENDURE,0,false); + skill_additional_effect(src, bl, CR_REFLECTSHIELD, 1, BF_WEAPON|BF_SHORT|BF_NORMAL,ATK_DEF,tick); + map_freeblock_unlock(); + } + + return 0; +} +/** + * Triggers aftercast delay for autocasted skills. + * @param src: Source data + * @param skill_id: Skill used + * @param skill_lv: Skill level used + * @param tick: Server tick + */ +void battle_autocast_aftercast(struct block_list* src, uint16 skill_id, uint16 skill_lv, t_tick tick) +{ + unit_data *ud = unit_bl2ud(src); + + if (ud) { + int autocast_tick = skill_delayfix(src, skill_id, skill_lv); + + if (DIFF_TICK(ud->canact_tick, tick + autocast_tick) < 0) { + ud->canact_tick = i64max(tick + autocast_tick, ud->canact_tick); + + if (battle_config.display_status_timers && src->type == BL_PC) + clif_status_change(src, EFST_POSTDELAY, 1, autocast_tick, 0, 0, 0); + } + } +} + +/** + * Triggers autocasted skills from super elemental supportive buffs. + * @param sd: Player data + * @param target: Target data + * @param skill_id: Skill used + * @param tick: Server tick + * @param flag: Special skill flags + */ +void battle_autocast_elembuff_skill(map_session_data* sd, struct block_list* target, uint16 skill_id, t_tick tick, int flag) +{ + uint16 skill_lv = pc_checkskill(sd, skill_id); + + skill_lv = max(1, skill_lv); + + sd->state.autocast = 1; + if (status_charge(&sd->bl, 0, skill_get_sp(skill_id, skill_lv))) { + skill_castend_damage_id(&sd->bl, target, skill_id, skill_lv, tick, flag); + battle_autocast_aftercast(&sd->bl, skill_id, skill_lv, tick); + } + sd->state.autocast = 0; +} + +/*========================================== + * Do a basic physical attack (call through unit_attack_timer) + *------------------------------------------*/ +enum damage_lv battle_weapon_attack(struct block_list* src, struct block_list* target, t_tick tick, int flag) { + map_session_data *sd = nullptr, *tsd = nullptr; + status_change *sc, *tsc; + int64 damage; + int skillv; + struct Damage wd; + bool vellum_damage = false; + + nullpo_retr(ATK_NONE, src); + nullpo_retr(ATK_NONE, target); + + if (src->prev == nullptr || target->prev == nullptr) + return ATK_NONE; + + sd = BL_CAST(BL_PC, src); + tsd = BL_CAST(BL_PC, target); + + status_data* sstatus = status_get_status_data(*src); + status_data* 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 = nullptr; + if (tsc && !tsc->count) + tsc = nullptr; + + 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) + { + short index = sd->equip_index[EQI_AMMO]; + if (index < 0) { + if (sd->weapontype1 > W_KATAR && sd->weapontype1 < W_HUUMA) + clif_skill_fail( *sd, 0, USESKILL_FAIL_NEED_MORE_BULLET ); + else + clif_arrow_fail( *sd, ARROWFAIL_NO_AMMO ); + 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]->subtype != AMMO_ARROW) { + clif_arrow_fail( *sd, ARROWFAIL_NO_AMMO ); + return ATK_NONE; + } + break; + case W_REVOLVER: + case W_RIFLE: + case W_GATLING: + case W_SHOTGUN: + if (sd->inventory_data[index]->subtype != AMMO_BULLET) { + clif_skill_fail( *sd, 0, USESKILL_FAIL_NEED_MORE_BULLET ); + return ATK_NONE; + } + break; + case W_GRENADE: + if (sd->inventory_data[index]->subtype != +#ifdef RENEWAL + AMMO_BULLET) { +#else + AMMO_GRENADE) { +#endif + clif_skill_fail( *sd, 0, USESKILL_FAIL_NEED_MORE_BULLET ); + return ATK_NONE; + } + break; + } + } + } + } + if (sc && sc->count) { + if (sc->getSCE(SC_CLOAKING) && !(sc->getSCE(SC_CLOAKING)->val4 & 2)) + status_change_end(src, SC_CLOAKING); + else if (sc->getSCE(SC_CLOAKINGEXCEED) && !(sc->getSCE(SC_CLOAKINGEXCEED)->val4 & 2)) + status_change_end(src, SC_CLOAKINGEXCEED); + else if (sc->getSCE(SC_NEWMOON) && --(sc->getSCE(SC_NEWMOON)->val2) <= 0) + status_change_end(src, SC_NEWMOON); + } + if (tsc && tsc->getSCE(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->getSCE(SC_AUTOCOUNTER)->val1; + + clif_skillcastcancel( *target ); //Remove the casting bar. [Skotlex] + clif_damage(*src, *target, tick, sstatus->amotion, 1, 0, 1, DMG_NORMAL, 0, false); //Display MISS. + status_change_end(target, SC_AUTOCOUNTER); + skill_attack(BF_WEAPON,target,target,src,KN_AUTOCOUNTER,skill_lv,tick,0); + return ATK_BLOCK; + } + } + + if( tsc && tsc->getSCE(SC_BLADESTOP_WAIT) && +#ifndef RENEWAL + status_get_class_(src) != CLASS_BOSS && +#endif + (src->type == BL_PC || tsd == nullptr || distance_bl(src, target) <= (tsd->status.weapon == W_FIST ? 1 : 2)) ) + { + uint16 skill_lv = tsc->getSCE(SC_BLADESTOP_WAIT)->val1; + int duration = skill_get_time2(MO_BLADESTOP,skill_lv); + +#ifdef RENEWAL + if (status_get_class_(src) == CLASS_BOSS) + duration = 2000; // Only lasts 2 seconds for Boss monsters +#endif + status_change_end(target, SC_BLADESTOP_WAIT); + if(sc_start4(src,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, DMG_NORMAL, 0, false); //Display MISS. + clif_bladestop(target, src->id, 1); + sc_start4(src,target, SC_BLADESTOP, 100, skill_lv, 0, 0, src->id, duration); + return ATK_BLOCK; + } + } + + if(sd && (skillv = pc_checkskill(sd,MO_TRIPLEATTACK)) > 0) { +#ifdef RENEWAL + int triple_rate = 30; //Base Rate +#else + int triple_rate = 30 - skillv; //Base Rate +#endif + + if (sc && sc->getSCE(SC_SKILLRATE_UP) && sc->getSCE(SC_SKILLRATE_UP)->val1 == MO_TRIPLEATTACK) { + triple_rate+= triple_rate*(sc->getSCE(SC_SKILLRATE_UP)->val2)/100; + status_change_end(src, SC_SKILLRATE_UP); + } + if (rnd()%100 < triple_rate) { + //Need to apply canact_tick here because it doesn't go through skill_castend_id + sd->ud.canact_tick = i64max(tick + skill_delayfix(src, MO_TRIPLEATTACK, skillv), sd->ud.canact_tick); + if( skill_attack(BF_WEAPON,src,src,target,MO_TRIPLEATTACK,skillv,tick,0) ) + return ATK_DEF; + return ATK_MISS; + } + } + + if (sc) { + if (sc->getSCE(SC_SACRIFICE)) { + uint16 skill_lv = sc->getSCE(SC_SACRIFICE)->val1; + damage_lv ret_val; + + if( --sc->getSCE(SC_SACRIFICE)->val2 <= 0 ) + status_change_end(src, SC_SACRIFICE); + + /** + * We need to calculate the DMG before the hp reduction, because it can kill the source. + * For further 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->getSCE(SC_MAGICALATTACK)) { + if( skill_attack(BF_MAGIC,src,src,target,NPC_MAGICALATTACK,sc->getSCE(SC_MAGICALATTACK)->val1,tick,0) ) + return ATK_DEF; + return ATK_MISS; + } + if( sc->getSCE(SC_GT_ENERGYGAIN) ) { + int spheres = 5; + + if( sc->getSCE(SC_RAISINGDRAGON) ) + spheres += sc->getSCE(SC_RAISINGDRAGON)->val1; + + if( sd && rnd()%100 < sc->getSCE(SC_GT_ENERGYGAIN)->val2 ) + pc_addspiritball(sd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, sc->getSCE(SC_GT_ENERGYGAIN)->val1), spheres); + } + } + + if( tsc && tsc->getSCE(SC_GT_ENERGYGAIN) ) { + int spheres = 5; + + if( tsc->getSCE(SC_RAISINGDRAGON) ) + spheres += tsc->getSCE(SC_RAISINGDRAGON)->val1; + + if( tsd && rnd()%100 < tsc->getSCE(SC_GT_ENERGYGAIN)->val2 ) + pc_addspiritball(tsd, skill_get_time2(SR_GENTLETOUCH_ENERGYGAIN, tsc->getSCE(SC_GT_ENERGYGAIN)->val1), spheres); + } + + if (tsc && tsc->getSCE(SC_MTF_MLEATKED) && rnd()%100 < tsc->getSCE(SC_MTF_MLEATKED)->val2) + clif_skill_nodamage(target, target, SM_ENDURE, tsc->getSCE(SC_MTF_MLEATKED)->val1, sc_start(src, target, SC_ENDURE, 100, tsc->getSCE(SC_MTF_MLEATKED)->val1, skill_get_time(SM_ENDURE, tsc->getSCE(SC_MTF_MLEATKED)->val1))); + + if(tsc && tsc->getSCE(SC_KAAHI) && tstatus->hp < tstatus->max_hp && status_charge(target, 0, tsc->getSCE(SC_KAAHI)->val3)) { + int hp_heal = tstatus->max_hp - tstatus->hp; + if (hp_heal > tsc->getSCE(SC_KAAHI)->val2) + hp_heal = tsc->getSCE(SC_KAAHI)->val2; + if (hp_heal) + status_heal(target, hp_heal, 0, 2); + } + + wd = battle_calc_attack(BF_WEAPON, src, target, 0, 0, flag); + + if (sd && wd.damage + wd.damage2 > 0 && battle_vellum_damage(sd, target, &wd)) + vellum_damage = true; + + if( sc && sc->count ) { + if (sc->getSCE(SC_EXEEDBREAK)) + status_change_end(src, SC_EXEEDBREAK); + if( sc->getSCE(SC_SPELLFIST) && !vellum_damage ){ + if (status_charge(src, 0, 20)) { + if (!is_infinite_defense(target, wd.flag)) { + struct Damage ad = battle_calc_attack(BF_MAGIC, src, target, sc->getSCE(SC_SPELLFIST)->val2, sc->getSCE(SC_SPELLFIST)->val3, flag | BF_SHORT); + + wd.damage = ad.damage; + DAMAGE_DIV_FIX(wd.damage, wd.div_); // Double the damage for multiple hits. + } else { + wd.damage = 1; + DAMAGE_DIV_FIX(wd.damage, wd.div_); + } + } else + status_change_end(src,SC_SPELLFIST); + } + if (sc->getSCE(SC_GIANTGROWTH) && (wd.flag&BF_SHORT) && rnd()%100 < sc->getSCE(SC_GIANTGROWTH)->val2 && !is_infinite_defense(target, wd.flag) && !vellum_damage) + wd.damage += wd.damage * 150 / 100; // 2.5 times damage + + if( sc->getSCE( SC_VIGOR ) && ( wd.flag&BF_SHORT ) && !is_infinite_defense( target, wd.flag ) && !vellum_damage ){ + int mod = 100 + sc->getSCE(SC_VIGOR)->val1 * 15; + + if (tstatus->race == RC_DEMIHUMAN || tstatus->race == RC_ANGEL) + mod += sc->getSCE(SC_VIGOR)->val1 * 10; + + wd.damage += wd.damage * mod / 100; + } + + if( sd && battle_config.arrow_decrement && sc->getSCE(SC_FEARBREEZE) && sc->getSCE(SC_FEARBREEZE)->val4 > 0) { + short idx = sd->equip_index[EQI_AMMO]; + if (idx >= 0 && sd->inventory.u.items_inventory[idx].amount >= sc->getSCE(SC_FEARBREEZE)->val4) { + pc_delitem(sd,idx,sc->getSCE(SC_FEARBREEZE)->val4,0,1,LOG_TYPE_CONSUME); + sc->getSCE(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->getSCE(SC_DUPLELIGHT) && (wd.flag & BF_SHORT)) { // Activates only from regular melee damage. Success chance is seperate for both duple light attacks. + uint16 duple_rate = 10 + 2 * sc->getSCE(SC_DUPLELIGHT)->val1; + + if (rand() % 100 < duple_rate) + skill_castend_damage_id(src, target, AB_DUPLELIGHT_MELEE, sc->getSCE(SC_DUPLELIGHT)->val1, tick, flag | SD_LEVEL); + + if (rand() % 100 < duple_rate) + skill_castend_damage_id(src, target, AB_DUPLELIGHT_MAGIC, sc->getSCE(SC_DUPLELIGHT)->val1, tick, flag | SD_LEVEL); + } + } + + wd.dmotion = clif_damage(*src, *target, tick, wd.amotion, wd.dmotion, wd.damage, wd.div_ , wd.type, wd.damage2, wd.isspdamage); + + 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 && su->group) { + if (su->group->skill_id == HT_BLASTMINE) + skill_blown(src, target, 3, -1, BLOWN_NONE); + if (su->group->skill_id == GN_WALLOFTHORN) { + if (--su->val2 <= 0) + skill_delunit(su); + } + } + } + + map_freeblock_lock(); + + if( !(tsc && tsc->getSCE(SC_DEVOTION)) && !vellum_damage && skill_check_shadowform(target, damage, wd.div_) ) { + if( !status_isdead(*target) ) + skill_additional_effect(src, target, 0, 0, wd.flag, wd.dmg_lv, tick); + if( wd.dmg_lv > ATK_BLOCK ) + skill_counter_additional_effect(src, target, 0, 0, wd.flag, tick); + } else + battle_delay_damage(tick, wd.amotion, src, target, wd.flag, 0, 0, damage, wd.dmg_lv, wd.dmotion, true, wd.isspdamage); + if( tsc ) { + if( tsc->getSCE(SC_DEVOTION) ) { + struct status_change_entry *sce = tsc->getSCE(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) ) + { + // Only trigger if the devoted player was hit + if( damage > 0 ){ + int64 devotion_damage = damage; + map_session_data* dsd = BL_CAST( BL_PC, d_bl ); + + // Needed to check the devotion master for Rebound Shield status. + status_change *d_sc = status_get_sc(d_bl); + + // The devoting player needs to stand up + if( dsd && pc_issit( dsd ) ){ + pc_setstand( dsd, true ); + skill_sit( dsd, 0 ); + } + + if (d_sc && d_sc->getSCE(SC_REBOUND_S)) + devotion_damage -= devotion_damage * d_sc->getSCE(SC_REBOUND_S)->val2 / 100; + + clif_damage(*d_bl, *d_bl, gettick(), wd.amotion, wd.dmotion, devotion_damage, 1, DMG_NORMAL, 0, false); + battle_fix_damage(src, d_bl, devotion_damage, 0, CR_DEVOTION); + } + } + else + status_change_end(target, SC_DEVOTION); + } + if (target->type == BL_PC && (wd.flag&BF_SHORT) && tsc->getSCE(SC_CIRCLE_OF_FIRE_OPTION)) { + s_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->getSCE(SC_CIRCLE_OF_FIRE_OPTION)->val1, DMG_SINGLE); + skill_attack(BF_WEAPON,&ed->bl,&ed->bl,src,EL_CIRCLE_OF_FIRE,tsc->getSCE(SC_CIRCLE_OF_FIRE_OPTION)->val1,tick,wd.flag); + } + } + if (tsc->getSCE(SC_WATER_SCREEN_OPTION)) { + struct block_list *e_bl = map_id2bl(tsc->getSCE(SC_WATER_SCREEN_OPTION)->val1); + + if (e_bl && !status_isdead(*e_bl)) { + clif_damage(*e_bl, *e_bl, tick, 0, 0, damage, wd.div_, DMG_NORMAL, 0, false); + battle_fix_damage(src, e_bl, damage, 0, EL_WATER_SCREEN); + } + } + } + if (sc && sc->getSCE(SC_AUTOSPELL) && rnd()%100 < sc->getSCE(SC_AUTOSPELL)->val4) { + int sp = 0; + uint16 skill_id = sc->getSCE(SC_AUTOSPELL)->val2; + uint16 skill_lv = sc->getSCE(SC_AUTOSPELL)->val3; + int i = rnd()%100; + if (sc->getSCE(SC_SPIRIT) && sc->getSCE(SC_SPIRIT)->val2 == SL_SAGE) + i = 0; //Max chance, no skill_lv reduction. [Skotlex] + //reduction only for skill_lv > 1 + if (skill_lv > 1) { + if (i >= 50) skill_lv /= 2; + else if (i >= 15) skill_lv--; + } + sp = skill_get_sp(skill_id,skill_lv) * 2 / 3; + + if (status_charge(src, 0, sp)) { + struct unit_data *ud = unit_bl2ud(src); + + 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 (ud) { + int autospell_tick = skill_delayfix(src, skill_id, skill_lv); + + if (DIFF_TICK(ud->canact_tick, tick + autospell_tick) < 0) { + ud->canact_tick = i64max(tick + autospell_tick, ud->canact_tick); + if (battle_config.display_status_timers && sd) + clif_status_change(src, EFST_POSTDELAY, 1, autospell_tick, 0, 0, 0); + } + } + } + } + if (sd) { + uint16 r_skill = 0, sk_idx = 0; + if( wd.flag&BF_WEAPON && sc && sc->getSCE(SC__AUTOSHADOWSPELL) && rnd()%100 < sc->getSCE(SC__AUTOSHADOWSPELL)->val3 && + (r_skill = (uint16)sc->getSCE(SC__AUTOSHADOWSPELL)->val1) && (sk_idx = skill_get_index(r_skill)) && + sd->status.skill[sk_idx].id != 0 && sd->status.skill[sk_idx].flag == SKILL_FLAG_PLAGIARIZED ) + { + if (r_skill != AL_HOLYLIGHT && r_skill != PR_MAGNUS) { + int r_lv = sc->getSCE(SC__AUTOSHADOWSPELL)->val2, type; + + if( (type = skill_get_casttype(r_skill)) == CAST_GROUND ) { + int maxcount = 0; + std::shared_ptr skill = skill_db.find(r_skill); + + if( !(BL_PC&battle_config.skill_reiteration) && skill->unit_flag[UF_NOREITERATION] ) + type = -1; + + if( BL_PC&battle_config.skill_nofootset && skill->unit_flag[UF_NOFOOTSET] ) + type = -1; + + if( BL_PC&battle_config.land_skill_limit && + (maxcount = skill_get_maxcount(r_skill, r_lv)) > 0 + ) { + unit_skillunit_maxcount(sd->ud, r_skill, maxcount); + + if( maxcount == 0 ) + type = -1; + } + + if( type != CAST_GROUND ){ + clif_skill_fail( *sd, r_skill ); + map_freeblock_unlock(); + return wd.dmg_lv; + } + } + + if (sd->state.autocast == 0) { + 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 = i64max(tick + skill_delayfix(src, r_skill, r_lv), sd->ud.canact_tick); + clif_status_change(src, EFST_POSTDELAY, 1, skill_delayfix(src, r_skill, r_lv), 0, 0, 1); + } + } + if (wd.flag&BF_WEAPON && sc && sc->getSCE(SC_FALLINGSTAR) && rand()%100 < sc->getSCE(SC_FALLINGSTAR)->val2) { + if (sd) + sd->state.autocast = 1; + if (status_charge(src, 0, skill_get_sp(SJ_FALLINGSTAR_ATK, sc->getSCE(SC_FALLINGSTAR)->val1))) + skill_castend_nodamage_id(src, src, SJ_FALLINGSTAR_ATK, sc->getSCE(SC_FALLINGSTAR)->val1, tick, flag); + if (sd) + sd->state.autocast = 0; + } + + if( sc ){ + if( sc->getSCE( SC_SERVANTWEAPON ) && sd->servantball > 0 && rnd_chance( 5 * sc->getSCE( SC_SERVANTWEAPON )->val1, 100 ) ){ + uint16 skill_id = DK_SERVANTWEAPON_ATK; + uint16 skill_lv = sc->getSCE(SC_SERVANTWEAPON)->val1; + + sd->state.autocast = 1; + pc_delservantball( *sd ); + skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + // TODO: Whats the official success chance? Is SP consumed for every autocast? [Rytech] + if( sc->getSCE(SC_DUPLELIGHT) && pc_checkskill(sd, CD_PETITIO) > 0 && rnd() % 100 < 20 ){ + uint16 skill_id = CD_PETITIO; + uint16 skill_lv = pc_checkskill( sd, CD_PETITIO ); + + sd->state.autocast = 1; + skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + if( sc->getSCE(SC_ABYSSFORCEWEAPON) && sd->abyssball > 0 && rnd_chance( 25, 100 ) ){ + uint16 skill_id = ABC_FROM_THE_ABYSS_ATK; + uint16 skill_lv = sc->getSCE(SC_ABYSSFORCEWEAPON)->val1; + + sd->state.autocast = 1; + pc_delabyssball( *sd ); + skill_castend_damage_id( src, target, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + // It has a success chance of triggering even tho the description says nothing about it. + // TODO: Need to find out what the official success chance is. [Rytech] + if( sc->getSCE(SC_ABYSSFORCEWEAPON) && rnd() % 100 < 20 ){ + uint16 skill_id = ABC_ABYSS_SQUARE; + uint16 skill_lv = pc_checkskill(sd, ABC_ABYSS_SQUARE); + + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + if( sc->getSCE( SC_AUTO_FIRING_LAUNCHER ) ){ + uint16 skill_id; + uint16 skill_lv; + + switch( sc->getSCE( SC_AUTO_FIRING_LAUNCHER )->val1 ){ + case 1: + skill_id = NW_BASIC_GRENADE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 6, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + break; + + case 2: + skill_id = NW_BASIC_GRENADE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 7, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + break; + + case 3: + skill_id = NW_HASTY_FIRE_IN_THE_HOLE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 3, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + skill_id = NW_BASIC_GRENADE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 8, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + break; + + case 4: + skill_id = NW_HASTY_FIRE_IN_THE_HOLE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 5, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + skill_id = NW_BASIC_GRENADE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 9, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + break; + + case 5: + skill_id = NW_GRENADES_DROPPING; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 3, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + skill_id = NW_HASTY_FIRE_IN_THE_HOLE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 7, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + + skill_id = NW_BASIC_GRENADE; + skill_lv = pc_checkskill( sd, skill_id ); + + if( skill_lv > 0 && rnd_chance( 10, 100 ) ){ + sd->state.autocast = 1; + skill_castend_pos2( src, target->x, target->y, skill_id, skill_lv, tick, flag ); + battle_autocast_aftercast( src, skill_id, skill_lv, tick ); + sd->state.autocast = 0; + } + break; + } + } + + // Autocasted skills from super elemental supportive buffs. + if (sc->getSCE(SC_FLAMETECHNIC_OPTION) && rnd() % 100 < 7) + battle_autocast_elembuff_skill(sd, target, MG_FIREBOLT, tick, flag); + if (sc->getSCE(SC_COLD_FORCE_OPTION) && rnd() % 100 < 7) + battle_autocast_elembuff_skill(sd, target, MG_COLDBOLT, tick, flag); + if (sc->getSCE(SC_GRACE_BREEZE_OPTION) && rnd() % 100 < 7) + battle_autocast_elembuff_skill(sd, target, MG_LIGHTNINGBOLT, tick, flag); + if (sc->getSCE(SC_EARTH_CARE_OPTION) && rnd() % 100 < 7) + battle_autocast_elembuff_skill(sd, target, WZ_EARTHSPIKE, tick, flag); + if (sc->getSCE(SC_DEEP_POISONING_OPTION) && rnd() % 100 < 7) + battle_autocast_elembuff_skill(sd, target, SO_POISON_BUSTER, tick, flag); + } + 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, tstatus->class_); + else + battle_drain(sd, target, wd.damage, wd.damage2, tstatus->race, tstatus->class_); + } + } + + if (tsc) { + if (damage > 0 && tsc->getSCE(SC_POISONREACT) && + (rnd()%100 < tsc->getSCE(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->getSCE(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); + } + } + + if (sd && tsc && wd.flag&BF_LONG && tsc->getSCE(SC_WINDSIGN) && rand()%100 < tsc->getSCE(SC_WINDSIGN)->val2) + status_heal(src, 0, 0, 1, 0); + + map_freeblock_unlock(); + return wd.dmg_lv; +} + +/*========================= + * Check for undead status + *------------------------- + * Credits: + * Original coder Skotlex + * Refactored by Baalberith + */ +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)->master) + src = (struct block_list*)((TBL_PET*)src)->master; + 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; +} + +bool battle_get_exception_ai(block_list &src) { + mob_data *md = BL_CAST(BL_MOB, &src); + + if (!md) + return false; + + switch (md->special_state.ai) { + case AI_ABR: + case AI_ATTACK: + case AI_BIONIC: + case AI_ZANZOU: + return true; + } + return false; +} + +/*========================================== + * Checks the state between two targets + * (enemy, friend, party, guild, etc) + *------------------------------------------ + * Usage: + * See battle.hpp 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) + * + * Credits: + * Original coder unknown + * Rewritten by Skotlex +*/ +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; + struct unit_data *ud = nullptr; + + nullpo_ret(src); + nullpo_ret(target); + + ud = unit_bl2ud(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)) == nullptr ) + t_bl = target; + + if( (s_bl = battle_get_master(src)) == nullptr ) + 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; + } + } + + struct map_data *mapdata = map_getmapdata(m); + + switch( target->type ) { // Checks on actual target + case BL_PC: { + 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->getSCE(SC_VOICEOFSIREN) && sc->getSCE(SC_VOICEOFSIREN)->val2 == target->id ) + return -1; + } + } + break; + case BL_MOB: + { + struct mob_data *md = ((TBL_MOB*)target); + + if (ud && ud->immune_attack) + return 0; + if(((md->special_state.ai == AI_SPHERE || //Marine Spheres + (md->special_state.ai == AI_FLORA && battle_config.summon_flora&1)) && s_bl->type == BL_PC && src->type != BL_MOB) || //Floras + (md->special_state.ai == AI_ZANZOU && t_bl->id != s_bl->id) || //Zanzou + (md->special_state.ai == AI_FAW && (t_bl->id != s_bl->id || (s_bl->type == BL_PC && src->type != BL_MOB))) + ){ //Targettable by players + state |= BCT_ENEMY; + strip_enemy = 0; + } + break; + } + case BL_SKILL: + { + TBL_SKILL *su = (TBL_SKILL*)target; + uint16 skill_id = battle_getcurrentskill(src); + if( !su || !su->group) + return 0; + if( skill_get_inf2(su->group->skill_id, INF2_ISTRAP) && su->group->unit_id != UNT_USED_TRAPS) { + if (!skill_id || su->group->skill_id == NPC_REVERBERATION || su->group->skill_id == WM_POEMOFNETHERWORLD) { + ; + } + else if (skill_get_inf2(skill_id, INF2_TARGETTRAP)) { // Only a few skills can target traps + switch (skill_id) { + case RK_DRAGONBREATH: + case RK_DRAGONBREATH_WATER: + case NC_SELFDESTRUCTION: + case NC_AXETORNADO: + case SR_SKYNETBLOW: + // Can only hit traps in PVP/GVG maps + if (!mapdata->getMapFlag(MF_PVP) && !mapdata->getMapFlag(MF_GVG)) + return 0; + break; + } + } + else + return 0; + state |= BCT_ENEMY; + strip_enemy = 0; + } else if (su->group->skill_id == WZ_ICEWALL || (su->group->skill_id == GN_WALLOFTHORN && skill_id != GN_CARTCANNON)) { + switch (skill_id) { + case RK_DRAGONBREATH: + case RK_DRAGONBREATH_WATER: + case NC_SELFDESTRUCTION: + case NC_AXETORNADO: + case SR_SKYNETBLOW: + // Can only hit icewall in PVP/GVG maps + if (!mapdata->getMapFlag(MF_PVP) && !mapdata->getMapFlag(MF_GVG)) + return 0; + break; + case HT_CLAYMORETRAP: + // Can't hit icewall + return 0; + default: + // Usually BCT_ALL stands for only hitting chars, but skills specifically set to hit traps also hit icewall + if ((flag&BCT_ALL) == BCT_ALL && !skill_get_inf2(skill_id, INF2_TARGETTRAP)) + return -1; + } + state |= BCT_ENEMY; + strip_enemy = 0; + } else //Excepting traps, Icewall, and Wall of Thorns, you should not be able to target skills. + return 0; + } + break; + case BL_MER: + case BL_HOM: + case BL_ELEM: + if (ud && ud->immune_attack) + return 0; + 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: { + map_session_data *sd; + status_change *sc = nullptr; + + if( t_bl == s_bl ) + break; + + sd = BL_CAST(BL_PC, t_bl); + sc = status_get_sc(t_bl); + + if( ((sd->state.block_action & PCBLOCK_IMMUNE) || (sc->getSCE(SC_KINGS_GRACE) && s_bl->type != BL_PC)) && flag&BCT_ENEMY ) + return 0; // Global immunity 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( md->guardian_data && md->guardian_data->guild_id && !mapdata_flag_gvg(mapdata) ) + 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 && flag & BCT_ENEMY) { + mob_data *md = BL_CAST(BL_MOB, t_bl); + + if (md->guardian_data || md->special_state.ai == AI_GUILD) + return 0; //pet may not attack Guardians/Emperium + } + break; + case BL_SKILL: { + struct skill_unit *su = (struct skill_unit *)src; + status_change* sc = status_get_sc(target); + if (!su || !su->group) + return 0; + + std::bitset inf2 = skill_db.find(su->group->skill_id)->inf2; + + if (su->group->src_id == target->id) { + if (inf2[INF2_NOTARGETSELF]) + return -1; + if (inf2[INF2_TARGETSELF]) + return 1; + } + //Status changes that prevent traps from triggering + if (sc && sc->count && inf2[INF2_ISTRAP]) { + if( sc->getSCE(SC_SIGHTBLASTER) && sc->getSCE(SC_SIGHTBLASTER)->val2 > 0 && sc->getSCE(SC_SIGHTBLASTER)->val4%2 == 0) + return -1; + } + } + break; + case BL_MER: + if (t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->mob_id == 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: + { + 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 && mapdata->getMapFlag(MF_PVP)) || (!battle_config.duel_allow_gvg && mapdata_flag_gvg(mapdata))) ) + { + 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( !sd->status.guild_id && t_bl->type == BL_MOB && ((TBL_MOB*)t_bl)->mob_id == MOBID_EMPERIUM && mapdata_flag_gvg(mapdata) ) + 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( md->guardian_data && md->guardian_data->guild_id && !mapdata_flag_gvg(mapdata) ) + 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 && !battle_get_exception_ai(*target) ) || + ( t_bl->type == BL_MOB && (((TBL_MOB*)t_bl)->special_state.ai == AI_NONE || ((TBL_MOB*)t_bl)->special_state.ai == AI_WAVEMODE )) + ) + state |= BCT_PARTY; //Normal mobs with no ai or with AI_WAVEMODE 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, icewall and traps + if(target->type&(BL_CHAR|BL_SKILL)) + 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( mapdata_flag_vs(mapdata) ) + { //Check rivalry settings. + int sbg_id = 0, tbg_id = 0; + if(mapdata->getMapFlag(MF_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) && !(mapdata->getMapFlag(MF_PVP) && mapdata->getMapFlag(MF_PVP_NOPARTY)) && !(mapdata_flag_gvg(mapdata) && mapdata->getMapFlag(MF_GVG_NOPARTY)) && (!mapdata->getMapFlag(MF_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( !(mapdata->getMapFlag(MF_PVP) && mapdata->getMapFlag(MF_PVP_NOGUILD)) && s_guild && t_guild && (s_guild == t_guild || (!(flag&BCT_SAMEGUILD) && guild_isallied(s_guild, t_guild))) && (!mapdata->getMapFlag(MF_BATTLEGROUND) || sbg_id == tbg_id) ) + state |= BCT_GUILD; + else + state |= BCT_ENEMY; + } + if( state&BCT_ENEMY && mapdata->getMapFlag(MF_BATTLEGROUND) && sbg_id && sbg_id == tbg_id ) + state &= ~BCT_ENEMY; + + if( state&BCT_ENEMY && battle_config.pk_mode && !mapdata_flag_gvg(mapdata) && 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 || (!(flag&BCT_SAMEGUILD) && 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] + if ( !check_distance_client_bl(src, bl, 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(nullptr,src->m,src->x,src->y,bl->x,bl->y,CELL_CHKWALL); +} + +/*============================================= + * Battle.conf settings and default/max values + *--------------------------------------------- + */ +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_CHAR|BL_HOM, BL_NUL, BL_ALL, }, + { "enable_baseatk_renewal", &battle_config.enable_baseatk_renewal, BL_ALL, 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, }, + { "cardfix_monster_physical", &battle_config.cardfix_monster_physical, 1, 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_NUL, 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, }, +#ifdef RENEWAL + { "traps_setting", &battle_config.traps_setting, 2, 0, 2, }, +#else + { "traps_setting", &battle_config.traps_setting, 0, 0, 2, }, +#endif + { "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, 300000, 0, INT_MAX, }, +#ifdef RENEWAL + { "guild_skill_relog_type", &battle_config.guild_skill_relog_type, 0, 0, 1, }, +#else + { "guild_skill_relog_type", &battle_config.guild_skill_relog_type, 1, 0, 1, }, +#endif + { "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, 4, 0, 4, }, + { "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_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, }, + { "pet_master_dead", &battle_config.pet_master_dead, 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, 0x0, 0x0, 0x7, }, + { "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, }, + { "hanbok_ignorepalette", &battle_config.hanbok_ignorepalette, 0, 0, 1, }, + { "oktoberfest_ignorepalette", &battle_config.oktoberfest_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, 0, 100 }, + { "natural_heal_weight_rate_renewal", &battle_config.natural_heal_weight_rate_renewal,70, 0, 100 }, + { "arrow_decrement", &battle_config.arrow_decrement, 1, 0, 2, }, + { "ammo_unequip", &battle_config.ammo_unequip, 1, 0, 1, }, + { "ammo_check_weapon", &battle_config.ammo_check_weapon, 1, 0, 1, }, + { "max_aspd", &battle_config.max_aspd, 190, 100, 199, }, + { "max_third_aspd", &battle_config.max_third_aspd, 193, 100, 199, }, + { "max_summoner_aspd", &battle_config.max_summoner_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_lv99", &battle_config.max_hp_lv99, 330000, 100, 1000000000, }, + { "max_hp_lv150", &battle_config.max_hp_lv150, 660000, 100, 1000000000, }, + { "max_hp", &battle_config.max_hp, 1100000, 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, SHRT_MAX, }, + { "max_baby_parameter", &battle_config.max_baby_parameter, 80, 10, SHRT_MAX, }, + { "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, 1, 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|8, }, + { "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, }, + { "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, }, + { "official_cell_stack_limit", &battle_config.official_cell_stack_limit, 1, 0, 255, }, + { "custom_cell_stack_limit", &battle_config.custom_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, 0, 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, 0, 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, 0, 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, 0, 10000, }, + { "item_drop_mvp_max", &battle_config.item_drop_mvp_max, 10000, 1, 10000, }, + { "item_drop_mvp_mode", &battle_config.item_drop_mvp_mode, 0, 0, 2, }, + { "item_drop_heal_min", &battle_config.item_drop_heal_min, 1, 0, 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, 0, 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, 0, 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, 0, 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_common_mvp", &battle_config.item_rate_common_mvp, 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_equip_mvp", &battle_config.item_rate_equip_mvp, 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_card_mvp", &battle_config.item_rate_card_mvp, 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_heal_mvp", &battle_config.item_rate_heal_mvp, 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_use_mvp", &battle_config.item_rate_use_mvp, 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, }, + { "prevent_logout_trigger", &battle_config.prevent_logout_trigger, 0xE, 0, 0xF, }, + { "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_mode_mes", &battle_config.pk_mode_mes, 1, 0, 1, }, + { "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, }, + { "pet_unequip_destroy", &battle_config.pet_unequip_destroy, 1, 0, 1, }, + { "multi_level_up", &battle_config.multi_level_up, 0, 0, 1, }, + { "multi_level_up_base", &battle_config.multi_level_up_base, 0, 0, MAX_LEVEL, }, + { "multi_level_up_job", &battle_config.multi_level_up_job, 0, 0, MAX_LEVEL, }, + { "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, }, + { "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, }, + { "skill_steal_random_options", &battle_config.skill_steal_random_options, 0, 0, 1, }, + { "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, 2, }, + { "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, }, + { "debuff_on_logout", &battle_config.debuff_on_logout, 0, 0, 1|2, }, + { "monster_ai", &battle_config.mob_ai, 0x0000, 0x0000, 0x1FFF, }, + { "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_MOBSKILL_LEVEL, 1, MAX_MOBSKILL_LEVEL, }, + { "retaliate_to_master", &battle_config.retaliate_to_master, 1, 0, 1, }, + { "rare_drop_announce", &battle_config.rare_drop_announce, 0, 0, 10000, }, + { "drop_rate_cap", &battle_config.drop_rate_cap, 9000, 0, 10000, }, + { "drop_rate_cap_vip", &battle_config.drop_rate_cap_vip, 9000, 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_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, 20000, }, + { "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, }, + { "vending_tax_min", &battle_config.vending_tax_min, 0, 0, MAX_ZENY, }, + { "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, 5000, 0, INT_MAX, }, + { "boss_active_time", &battle_config.boss_active_time, 5000, 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, 80, 0, 100, }, + { "display_status_timers", &battle_config.display_status_timers, 1, 0, 1, }, + { "skill_add_heal_rate", &battle_config.skill_add_heal_rate, 487, 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, }, + { "knockback_left", &battle_config.knockback_left, 1, 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, }, + { "land_protector_behavior", &battle_config.land_protector_behavior, 0, 0, 1, }, + { "npc_emotion_behavior", &battle_config.npc_emotion_behavior, 0, 0, 1, }, +// 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, 135, 10, SHRT_MAX, }, + { "max_baby_third_parameter", &battle_config.max_baby_third_parameter, 108, 10, SHRT_MAX, }, + { "max_trans_parameter", &battle_config.max_trans_parameter, 99, 10, SHRT_MAX, }, + { "max_third_trans_parameter", &battle_config.max_third_trans_parameter, 135, 10, SHRT_MAX, }, + { "max_extended_parameter", &battle_config.max_extended_parameter, 125, 10, SHRT_MAX, }, + { "max_summoner_parameter", &battle_config.max_summoner_parameter, 120, 10, SHRT_MAX, }, + { "max_fourth_parameter", &battle_config.max_fourth_parameter, 135, 10, SHRT_MAX, }, + { "skill_amotion_leniency", &battle_config.skill_amotion_leniency, 0, 0, 300 }, + { "mvp_tomb_enabled", &battle_config.mvp_tomb_enabled, 1, 0, 1 }, + { "mvp_tomb_delay", &battle_config.mvp_tomb_delay, 9000, 0, INT_MAX, }, + { "feature.atcommand_suggestions", &battle_config.atcommand_suggestions_enabled, 0, 0, 1 }, + { "min_npc_vendchat_distance", &battle_config.min_npc_vendchat_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, }, + { "skill_trap_type", &battle_config.skill_trap_type, 0, 0, 3, }, + { "allow_consume_restricted_item", &battle_config.allow_consume_restricted_item, 1, 0, 1, }, + { "allow_equip_restricted_item", &battle_config.allow_equip_restricted_item, 1, 0, 1, }, + { "max_walk_path", &battle_config.max_walk_path, 17, 1, MAX_WALKPATH, }, +#ifdef RENEWAL + { "item_enabled_npc", &battle_config.item_enabled_npc, 0, 0, 1, }, +#else + { "item_enabled_npc", &battle_config.item_enabled_npc, 1, 0, 1, }, +#endif + { "item_flooritem_check", &battle_config.item_onfloor, 1, 0, 1, }, + { "bowling_bash_area", &battle_config.bowling_bash_area, 0, 0, 20, }, + { "drop_rateincrease", &battle_config.drop_rateincrease, 0, 0, 1, }, + { "feature.auction", &battle_config.feature_auction, 0, 0, 2, }, + { "feature.banking", &battle_config.feature_banking, 1, 0, 1, }, +#ifdef VIP_ENABLE + { "vip_storage_increase", &battle_config.vip_storage_increase, 300, 0, MAX_STORAGE-MIN_STORAGE, }, +#else + { "vip_storage_increase", &battle_config.vip_storage_increase, 300, 0, MAX_STORAGE, }, +#endif + { "vip_base_exp_increase", &battle_config.vip_base_exp_increase, 50, 0, INT_MAX, }, + { "vip_job_exp_increase", &battle_config.vip_job_exp_increase, 50, 0, INT_MAX, }, + { "vip_exp_penalty_base", &battle_config.vip_exp_penalty_base, 100, 0, INT_MAX, }, + { "vip_exp_penalty_job", &battle_config.vip_exp_penalty_job, 100, 0, INT_MAX, }, + { "vip_zeny_penalty", &battle_config.vip_zeny_penalty, 0, 0, INT_MAX, }, + { "vip_bm_increase", &battle_config.vip_bm_increase, 2, 0, INT_MAX, }, + { "vip_drop_increase", &battle_config.vip_drop_increase, 50, 0, INT_MAX, }, + { "vip_gemstone", &battle_config.vip_gemstone, 2, 0, 2, }, + { "vip_disp_rate", &battle_config.vip_disp_rate, 1, 0, 1, }, + { "mon_trans_disable_in_gvg", &battle_config.mon_trans_disable_in_gvg, 0, 0, 1, }, + { "homunculus_S_growth_level", &battle_config.hom_S_growth_level, 99, 0, MAX_LEVEL, }, + { "discount_item_point_shop", &battle_config.discount_item_point_shop, 0, 0, 3, }, + { "update_enemy_position", &battle_config.update_enemy_position, 0, 0, 1, }, + { "devotion_rdamage", &battle_config.devotion_rdamage, 0, 0, 100, }, + { "feature.autotrade", &battle_config.feature_autotrade, 1, 0, 1, }, + { "feature.autotrade_direction", &battle_config.feature_autotrade_direction, 4, -1, 7, }, + { "feature.autotrade_head_direction", &battle_config.feature_autotrade_head_direction,0, -1, 2, }, + { "feature.autotrade_sit", &battle_config.feature_autotrade_sit, 1, -1, 1, }, + { "feature.autotrade_open_delay", &battle_config.feature_autotrade_open_delay, 5000, 1000, INT_MAX, }, + { "disp_servervip_msg", &battle_config.disp_servervip_msg, 0, 0, 1, }, + { "warg_can_falcon", &battle_config.warg_can_falcon, 0, 0, 1, }, + { "path_blown_halt", &battle_config.path_blown_halt, 1, 0, 1, }, + { "rental_mount_speed_boost", &battle_config.rental_mount_speed_boost, 25, 0, 100, }, + { "feature.warp_suggestions", &battle_config.warp_suggestions_enabled, 0, 0, 1, }, + { "taekwon_mission_mobname", &battle_config.taekwon_mission_mobname, 0, 0, 2, }, + { "teleport_on_portal", &battle_config.teleport_on_portal, 0, 0, 1, }, + { "cart_revo_knockback", &battle_config.cart_revo_knockback, 1, 0, 1, }, + { "guild_notice_changemap", &battle_config.guild_notice_changemap, 2, 0, 2, }, + { "transcendent_status_points", &battle_config.transcendent_status_points, 52, 1, INT_MAX, }, + { "taekwon_ranker_min_lv", &battle_config.taekwon_ranker_min_lv, 90, 1, MAX_LEVEL, }, + { "revive_onwarp", &battle_config.revive_onwarp, 1, 0, 1, }, + { "fame_taekwon_mission", &battle_config.fame_taekwon_mission, 1, 0, INT_MAX, }, + { "fame_refine_lv1", &battle_config.fame_refine_lv1, 1, 0, INT_MAX, }, + { "fame_refine_lv1", &battle_config.fame_refine_lv1, 1, 0, INT_MAX, }, + { "fame_refine_lv2", &battle_config.fame_refine_lv2, 25, 0, INT_MAX, }, + { "fame_refine_lv3", &battle_config.fame_refine_lv3, 1000, 0, INT_MAX, }, + { "fame_forge", &battle_config.fame_forge, 10, 0, INT_MAX, }, + { "fame_pharmacy_3", &battle_config.fame_pharmacy_3, 1, 0, INT_MAX, }, + { "fame_pharmacy_5", &battle_config.fame_pharmacy_5, 3, 0, INT_MAX, }, + { "fame_pharmacy_7", &battle_config.fame_pharmacy_7, 10, 0, INT_MAX, }, + { "fame_pharmacy_10", &battle_config.fame_pharmacy_10, 50, 0, INT_MAX, }, + { "mail_delay", &battle_config.mail_delay, 1000, 1000, INT_MAX, }, + { "at_monsterignore", &battle_config.autotrade_monsterignore, 0, 0, 1, }, + { "idletime_option", &battle_config.idletime_option, 0x7C1F, 1, 0xFFFF, }, + { "spawn_direction", &battle_config.spawn_direction, 0, 0, 1, }, + { "arrow_shower_knockback", &battle_config.arrow_shower_knockback, 1, 0, 1, }, + { "devotion_rdamage_skill_only", &battle_config.devotion_rdamage_skill_only, 1, 0, 1, }, + { "max_extended_aspd", &battle_config.max_extended_aspd, 193, 100, 199, }, + { "monster_chase_refresh", &battle_config.mob_chase_refresh, 30, 0, MAX_MINCHASE, }, + { "mob_icewall_walk_block", &battle_config.mob_icewall_walk_block, 75, 0, 255, }, + { "boss_icewall_walk_block", &battle_config.boss_icewall_walk_block, 0, 0, 255, }, + { "snap_dodge", &battle_config.snap_dodge, 0, 0, 1, }, + { "stormgust_knockback", &battle_config.stormgust_knockback, 1, 0, 1, }, + { "default_fixed_castrate", &battle_config.default_fixed_castrate, 20, 0, 100, }, + { "default_bind_on_equip", &battle_config.default_bind_on_equip, BOUND_CHAR, BOUND_NONE, BOUND_MAX-1, }, + { "pet_ignore_infinite_def", &battle_config.pet_ignore_infinite_def, 0, 0, 1, }, + { "homunculus_evo_intimacy_need", &battle_config.homunculus_evo_intimacy_need, 91100, 0, INT_MAX, }, + { "homunculus_evo_intimacy_reset", &battle_config.homunculus_evo_intimacy_reset, 1000, 0, INT_MAX, }, + { "monster_loot_search_type", &battle_config.monster_loot_search_type, 1, 0, 1, }, + { "feature.roulette", &battle_config.feature_roulette, 1, 0, 1, }, + { "feature.roulette_bonus_reward", &battle_config.feature_roulette_bonus_reward, 1, 0, 1, }, + { "monster_hp_bars_info", &battle_config.monster_hp_bars_info, 1, 0, 1, }, + { "min_body_style", &battle_config.min_body_style, 0, 0, SHRT_MAX, }, + { "max_body_style", &battle_config.max_body_style, 1, 0, SHRT_MAX, }, + { "save_body_style", &battle_config.save_body_style, 1, 0, 1, }, + { "monster_eye_range_bonus", &battle_config.mob_eye_range_bonus, 0, 0, 10, }, + { "monster_stuck_warning", &battle_config.mob_stuck_warning, 0, 0, 1, }, + { "skill_eightpath_algorithm", &battle_config.skill_eightpath_algorithm, 1, 0, 1, }, + { "skill_eightpath_same_cell", &battle_config.skill_eightpath_same_cell, 1, 0, 1, }, + { "death_penalty_maxlv", &battle_config.death_penalty_maxlv, 0, 0, 3, }, + { "exp_cost_redemptio", &battle_config.exp_cost_redemptio, 1, 0, 100, }, + { "exp_cost_redemptio_limit", &battle_config.exp_cost_redemptio_limit, 5, 0, MAX_PARTY, }, + { "mvp_exp_reward_message", &battle_config.mvp_exp_reward_message, 0, 0, 1, }, + { "can_damage_skill", &battle_config.can_damage_skill, 1, 0, BL_ALL, }, + { "atcommand_levelup_events", &battle_config.atcommand_levelup_events, 0, 0, 1, }, + { "atcommand_disable_npc", &battle_config.atcommand_disable_npc, 1, 0, 1, }, + { "block_account_in_same_party", &battle_config.block_account_in_same_party, 1, 0, 1, }, + { "tarotcard_equal_chance", &battle_config.tarotcard_equal_chance, 0, 0, 1, }, + { "change_party_leader_samemap", &battle_config.change_party_leader_samemap, 1, 0, 1, }, + { "dispel_song", &battle_config.dispel_song, 0, 0, 1, }, + { "guild_maprespawn_clones", &battle_config.guild_maprespawn_clones, 0, 0, 1, }, + { "hide_fav_sell", &battle_config.hide_fav_sell, 0, 0, 1, }, + { "mail_daily_count", &battle_config.mail_daily_count, 100, 0, INT32_MAX, }, + { "mail_zeny_fee", &battle_config.mail_zeny_fee, 2, 0, 100, }, + { "mail_attachment_price", &battle_config.mail_attachment_price, 2500, 0, INT32_MAX, }, + { "mail_attachment_weight", &battle_config.mail_attachment_weight, 2000, 0, INT32_MAX, }, + { "banana_bomb_duration", &battle_config.banana_bomb_duration, 0, 0, UINT16_MAX, }, + { "guild_leaderchange_delay", &battle_config.guild_leaderchange_delay, 1440, 0, INT32_MAX, }, + { "guild_leaderchange_woe", &battle_config.guild_leaderchange_woe, 0, 0, 1, }, + { "guild_alliance_onlygm", &battle_config.guild_alliance_onlygm, 0, 0, 1, }, + { "feature.achievement", &battle_config.feature_achievement, 1, 0, 1, }, + { "allow_bound_sell", &battle_config.allow_bound_sell, 0, 0, 0xF, }, + { "autoloot_adjust", &battle_config.autoloot_adjust, 0, 0, 1, }, + { "feature.petevolution", &battle_config.feature_petevolution, 1, 0, 1, }, + { "feature.petautofeed", &battle_config.feature_pet_autofeed, 1, 0, 1, }, + { "feature.pet_autofeed_rate", &battle_config.feature_pet_autofeed_rate, 89, 0, 100, }, + { "pet_autofeed_always", &battle_config.pet_autofeed_always, 1, 0, 1, }, + { "broadcast_hide_name", &battle_config.broadcast_hide_name, 2, 0, NAME_LENGTH, }, + { "skill_drop_items_full", &battle_config.skill_drop_items_full, 0, 0, 1, }, + { "switch_remove_edp", &battle_config.switch_remove_edp, 2, 0, 3, }, + { "feature.homunculus_autofeed", &battle_config.feature_homunculus_autofeed, 1, 0, 1, }, + { "feature.homunculus_autofeed_rate", &battle_config.feature_homunculus_autofeed_rate,30, 0, 100, }, + { "summoner_race", &battle_config.summoner_race, RC_PLAYER_DORAM, RC_FORMLESS, RC_PLAYER_DORAM, }, + { "summoner_size", &battle_config.summoner_size, SZ_SMALL, SZ_SMALL, SZ_BIG, }, + { "homunculus_autofeed_always", &battle_config.homunculus_autofeed_always, 1, 0, 1, }, + { "feature.attendance", &battle_config.feature_attendance, 1, 0, 1, }, + { "feature.privateairship", &battle_config.feature_privateairship, 1, 0, 1, }, + { "rental_transaction", &battle_config.rental_transaction, 1, 0, 1, }, + { "min_shop_buy", &battle_config.min_shop_buy, 1, 0, INT_MAX, }, + { "min_shop_sell", &battle_config.min_shop_sell, 0, 0, INT_MAX, }, + { "feature.equipswitch", &battle_config.feature_equipswitch, 1, 0, 1, }, + { "pet_walk_speed", &battle_config.pet_walk_speed, 1, 1, 3, }, + { "blacksmith_fame_refine_threshold", &battle_config.blacksmith_fame_refine_threshold,10, 1, MAX_REFINE, }, + { "mob_nopc_idleskill_rate", &battle_config.mob_nopc_idleskill_rate, 100, 0, 100, }, + { "mob_nopc_move_rate", &battle_config.mob_nopc_move_rate, 100, 0, 100, }, + { "boss_nopc_idleskill_rate", &battle_config.boss_nopc_idleskill_rate, 100, 0, 100, }, + { "boss_nopc_move_rate", &battle_config.boss_nopc_move_rate, 100, 0, 100, }, + { "hom_idle_no_share", &battle_config.hom_idle_no_share, 0, 0, INT_MAX, }, + { "idletime_hom_option", &battle_config.idletime_hom_option, 0x1F, 0x1, 0xFFF, }, + { "devotion_standup_fix", &battle_config.devotion_standup_fix, 1, 0, 1, }, + { "feature.bgqueue", &battle_config.feature_bgqueue, 1, 0, 1, }, + { "bgqueue_nowarp_mapflag", &battle_config.bgqueue_nowarp_mapflag, 0, 0, 1, }, + { "homunculus_exp_gain", &battle_config.homunculus_exp_gain, 10, 0, 100, }, + { "rental_item_novalue", &battle_config.rental_item_novalue, 1, 0, 1, }, + { "ping_timer_inverval", &battle_config.ping_timer_interval, 30, 0, 99999999, }, + { "ping_time", &battle_config.ping_time, 20, 0, 99999999, }, + { "show_skill_scale", &battle_config.show_skill_scale, 1, 0, 1, }, + { "achievement_mob_share", &battle_config.achievement_mob_share, 0, 0, 1, }, + { "slave_stick_with_master", &battle_config.slave_stick_with_master, 0, 0, 1, }, + { "at_logout_event", &battle_config.at_logout_event, 1, 0, 1, }, + { "homunculus_starving_rate", &battle_config.homunculus_starving_rate, 10, 0, 100, }, + { "homunculus_starving_delay", &battle_config.homunculus_starving_delay, 20000, 0, INT_MAX, }, + { "drop_connection_on_quit", &battle_config.drop_connection_on_quit, 0, 0, 1, }, + { "mob_spawn_variance", &battle_config.mob_spawn_variance, 1, 0, 3, }, + { "mercenary_autoloot", &battle_config.mercenary_autoloot, 0, 0, 1, }, + { "mer_idle_no_share" , &battle_config.mer_idle_no_share, 0, 0, INT_MAX, }, + { "idletime_mer_option", &battle_config.idletime_mer_option, 0x1F, 0x1, 0xFFF, }, + { "feature.refineui", &battle_config.feature_refineui, 1, 0, 1, }, + { "rndopt_drop_pillar", &battle_config.rndopt_drop_pillar, 1, 0, 1, }, + { "pet_legacy_formula", &battle_config.pet_legacy_formula, 0, 0, 1, }, + { "pet_distance_check", &battle_config.pet_distance_check, 5, 0, 50, }, + { "pet_hide_check", &battle_config.pet_hide_check, 1, 0, 1, }, + { "instance_block_leave", &battle_config.instance_block_leave, 1, 0, 1, }, + { "instance_block_leaderchange", &battle_config.instance_block_leaderchange, 1, 0, 1, }, + { "instance_block_invite", &battle_config.instance_block_invite, 1, 0, 1, }, + { "instance_block_expulsion", &battle_config.instance_block_expulsion, 1, 0, 1, }, + + // 4th Job Stuff + { "use_traitpoint_table", &battle_config.use_traitpoint_table, 1, 0, 1, }, + { "trait_points_job_change", &battle_config.trait_points_job_change, 7, 1, 1000, }, + { "max_trait_parameter", &battle_config.max_trait_parameter, 100, 10, SHRT_MAX, }, + { "max_res_mres_ignored", &battle_config.max_res_mres_ignored, 50, 0, 100, }, + { "max_ap", &battle_config.max_ap, 200, 100, 1000000000, }, + { "ap_rate", &battle_config.ap_rate, 100, 1, INT_MAX, }, + { "restart_ap_rate", &battle_config.restart_ap_rate, 0, 0, 100, }, + { "loose_ap_on_death", &battle_config.loose_ap_on_death, 1, 0, 1, }, + { "loose_ap_on_map", &battle_config.loose_ap_on_map, 1, 0, 1, }, + { "keep_ap_on_logout", &battle_config.keep_ap_on_logout, 1, 0, 1, }, + { "attack_machine_level_difference", &battle_config.attack_machine_level_difference, 15, 0, INT_MAX, }, + + { "feature.barter", &battle_config.feature_barter, 1, 0, 1, }, + { "feature.barter_extended", &battle_config.feature_barter_extended, 1, 0, 1, }, + { "feature.itemlink", &battle_config.feature_itemlink, 1, 0, 1, }, + { "feature.mesitemlink", &battle_config.feature_mesitemlink, 1, 0, 1, }, + { "feature.mesitemlink_brackets", &battle_config.feature_mesitemlink_brackets, 0, 0, 1, }, + { "feature.mesitemlink_dbname", &battle_config.feature_mesitemlink_dbname, 0, 0, 1, }, + { "break_mob_equip", &battle_config.break_mob_equip, 0, 0, 1, }, + { "macro_detection_retry", &battle_config.macro_detection_retry, 3, 1, INT_MAX, }, + { "macro_detection_timeout", &battle_config.macro_detection_timeout, 60000, 0, INT_MAX, }, + { "macro_detection_punishment", &battle_config.macro_detection_punishment, 0, 0, 1, }, + { "macro_detection_punishment_time", &battle_config.macro_detection_punishment_time, 0, 0, INT_MAX, }, + + { "feature.dynamicnpc_timeout", &battle_config.feature_dynamicnpc_timeout, 1000, 60000, INT_MAX, }, + { "feature.dynamicnpc_rangex", &battle_config.feature_dynamicnpc_rangex, 2, 0, INT_MAX, }, + { "feature.dynamicnpc_rangey", &battle_config.feature_dynamicnpc_rangey, 2, 0, INT_MAX, }, + { "feature.dynamicnpc_direction", &battle_config.feature_dynamicnpc_direction, 0, 0, 1, }, + + { "mob_respawn_time", &battle_config.mob_respawn_time, 1000, 1000, INT_MAX, }, + { "mob_unlock_time", &battle_config.mob_unlock_time, 2000, 0, INT_MAX, }, + { "map_edge_size", &battle_config.map_edge_size, 15, 1, 40, }, + { "randomize_center_cell", &battle_config.randomize_center_cell, 1, 0, 1, }, + + { "feature.stylist", &battle_config.feature_stylist, 1, 0, 1, }, + { "feature.banking_state_enforce", &battle_config.feature_banking_state_enforce, 0, 0, 1, }, +#ifdef RENEWAL + { "feature.instance_allow_reconnect", &battle_config.instance_allow_reconnect, 1, 0, 1, }, +#else + { "feature.instance_allow_reconnect", &battle_config.instance_allow_reconnect, 0, 0, 1, }, +#endif + { "synchronize_damage", &battle_config.synchronize_damage, 0, 0, 1, }, + { "item_stacking", &battle_config.item_stacking, 1, 0, 1, }, +#ifdef RENEWAL + { "hom_delay_reset_vaporize", &battle_config.hom_delay_reset_vaporize, 0, 0, 1, }, + { "hom_delay_reset_warp", &battle_config.hom_delay_reset_warp, 0, 0, 1, }, +#else + { "hom_delay_reset_vaporize", &battle_config.hom_delay_reset_vaporize, 1, 0, 1, }, + { "hom_delay_reset_warp", &battle_config.hom_delay_reset_warp, 1, 0, 1, }, +#endif + +#include +}; + +/*========================== + * Set battle settings + *--------------------------*/ +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; +} + +/*=========================== + * Get battle settings + *---------------------------*/ +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; +} + +/*====================== + * Set default settings + *----------------------*/ +void battle_set_defaults() +{ + int i; + for (i = 0; i < ARRAYLENGTH(battle_data); i++) + *battle_data[i].val = battle_data[i].defval; +} + +/*================================== + * Cap certain battle.conf settings + *----------------------------------*/ +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_summoner_aspd = 2000 - battle_config.max_summoner_aspd * 10; + battle_config.max_extended_aspd = 2000 - battle_config.max_extended_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 < 20100000 + if( battle_config.feature_mesitemlink ){ + ShowWarning( "conf/battle/feature.conf:mesitemlink is enabled but it requires PACKETVER 2010-01-01 or newer, disabling...\n" ); + battle_config.feature_mesitemlink = 0; + } +#elif PACKETVER == 20151029 || PACKETVER == 20151104 + // The feature is broken on those two clients or maybe even more. For more details check ItemDatabase::create_item_link_for_mes [Lemongrass] + if( battle_config.feature_mesitemlink ){ + ShowWarning( "conf/battle/feature.conf:mesitemlink is enabled but it is broken on this specific PACKETVER, disabling...\n" ); + battle_config.feature_mesitemlink = 0; + } +#endif + +#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 + +#if PACKETVER < 20120101 + if (battle_config.feature_bgqueue) { + ShowWarning("conf/battle/feature.conf:bgqueue is enabled but it requires PACKETVER 2012-01-01 or newer, disabling...\n"); + battle_config.feature_bgqueue = 0; + } +#endif + +#if PACKETVER > 20120000 && PACKETVER < 20130515 /* Exact date (when it started) not known */ + if (battle_config.feature_auction) { + ShowWarning("conf/battle/feature.conf:feature.auction is enabled but it is not stable on PACKETVER " EXPAND_AND_QUOTE(PACKETVER) ", disabling...\n"); + ShowWarning("conf/battle/feature.conf:feature.auction change value to '2' to silence this warning and maintain it enabled\n"); + battle_config.feature_auction = 0; + } +#elif PACKETVER >= 20141112 + if (battle_config.feature_auction) { + ShowWarning("conf/battle/feature.conf:feature.auction is enabled but it is not available for clients from 2014-11-12 on, disabling...\n"); + ShowWarning("conf/battle/feature.conf:feature.auction change value to '2' to silence this warning and maintain it enabled\n"); + battle_config.feature_auction = 0; + } +#endif + +#if PACKETVER < 20130724 + if (battle_config.feature_banking) { + ShowWarning("conf/battle/feature.conf banking is enabled but it requires PACKETVER 2013-07-24 or newer, disabling...\n"); + battle_config.feature_banking = 0; + } +#endif + +#if PACKETVER < 20131223 + if (battle_config.mvp_exp_reward_message) { + ShowWarning("conf/battle/client.conf MVP EXP reward message is enabled but it requires PACKETVER 2013-12-23 or newer, disabling...\n"); + battle_config.mvp_exp_reward_message = 0; + } +#endif + +#if PACKETVER < 20141022 + if (battle_config.feature_roulette) { + ShowWarning("conf/battle/feature.conf roulette is enabled but it requires PACKETVER 2014-10-22 or newer, disabling...\n"); + battle_config.feature_roulette = 0; + } +#endif + +#if PACKETVER < 20150513 + if (battle_config.feature_achievement) { + ShowWarning("conf/battle/feature.conf achievement is enabled but it requires PACKETVER 2015-05-13 or newer, disabling...\n"); + battle_config.feature_achievement = 0; + } +#endif + +#if PACKETVER < 20151104 + if( battle_config.feature_itemlink ){ + ShowWarning( "conf/battle/feature.conf:itemlink is enabled but it requires PACKETVER 2015-11-04 or newer, disabling...\n" ); + battle_config.feature_itemlink = 0; + } +#endif + +#if PACKETVER < 20151104 + if( battle_config.feature_stylist ){ + ShowWarning("conf/battle/feature.conf stylist is enabled but it requires PACKETVER 2015-11-04 or newer, disabling...\n"); + battle_config.feature_stylist = 0; + } +#endif + +#if PACKETVER < 20141008 + if (battle_config.feature_petevolution) { + ShowWarning("conf/battle/feature.conf petevolution is enabled but it requires PACKETVER 2014-10-08 or newer, disabling...\n"); + battle_config.feature_petevolution = 0; + } + if (battle_config.feature_pet_autofeed) { + ShowWarning("conf/battle/feature.conf pet auto feed is enabled but it requires PACKETVER 2014-10-08 or newer, disabling...\n"); + battle_config.feature_pet_autofeed = 0; + } +#endif + +#if PACKETVER < 20161012 + if (battle_config.feature_refineui) { + ShowWarning("conf/battle/feature.conf refine UI is enabled but it requires PACKETVER 2016-10-12 or newer, disabling...\n"); + battle_config.feature_refineui = 0; + } +#endif + +#if PACKETVER < 20170208 + if (battle_config.feature_equipswitch) { + ShowWarning("conf/battle/feature.conf equip switch is enabled but it requires PACKETVER 2017-02-08 or newer, disabling...\n"); + battle_config.feature_equipswitch = 0; + } +#endif + +#if PACKETVER < 20170920 + if( battle_config.feature_homunculus_autofeed ){ + ShowWarning("conf/battle/feature.conf homunculus autofeeding is enabled but it requires PACKETVER 2017-09-20 or newer, disabling...\n"); + battle_config.feature_homunculus_autofeed = 0; + } +#endif + +#if PACKETVER < 20180307 + if( battle_config.feature_attendance ){ + ShowWarning("conf/battle/feature.conf attendance system is enabled but it requires PACKETVER 2018-03-07 or newer, disabling...\n"); + battle_config.feature_attendance = 0; + } +#endif + +#if PACKETVER < 20180321 + if( battle_config.feature_privateairship ){ + ShowWarning("conf/battle/feature.conf private airship system is enabled but it requires PACKETVER 2018-03-21 or newer, disabling...\n"); + battle_config.feature_privateairship = 0; + } +#endif + +#if !( PACKETVER_MAIN_NUM >= 20190116 || PACKETVER_RE_NUM >= 20190116 || PACKETVER_ZERO_NUM >= 20181226 ) + if( battle_config.feature_barter ){ + ShowWarning("conf/battle/feature.conf barter shop system is enabled but it requires PACKETVER 2019-01-16 or newer, disabling...\n"); + battle_config.feature_barter = 0; + } +#endif + +#if !( PACKETVER_MAIN_NUM >= 20191120 || PACKETVER_RE_NUM >= 20191106 || PACKETVER_ZERO_NUM >= 20191127 ) + if( battle_config.feature_barter_extended ){ + ShowWarning("conf/battle/feature.conf extended barter shop system is enabled but it requires PACKETVER 2019-11-06 or newer, disabling...\n"); + battle_config.feature_barter_extended = 0; + } +#endif + +#ifndef CELL_NOSTACK + if (battle_config.custom_cell_stack_limit != 1) + ShowWarning("Battle setting 'custom_cell_stack_limit' takes no effect as this server was compiled without Cell Stack Limit support.\n"); +#endif + +#ifdef MAP_GENERATOR + battle_config.dynamic_mobs = 1; +#endif +} + +/*===================================== + * Read battle.conf settings from file + *-------------------------------------*/ +int battle_config_read(const char* cfgName) +{ + FILE* fp; + static int count = 0; + + if (count == 0) + battle_set_defaults(); + + count++; + + fp = fopen(cfgName,"r"); + if (fp == nullptr) + ShowError("File not found: %s\n", cfgName); + else { + char line[1024], w1[1024], w2[1024]; + + 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( strcmpi( w1, "atcommand_symbol" ) == 0 ){ + const char* symbol = &w2[0]; + + 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; + }else if( strcmpi( w1, "charcommand_symbol" ) == 0 ){ + const char* symbol = &w2[0]; + + 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; + }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; +} + +/*========================== + * initialize battle timer + *--------------------------*/ +void do_init_battle(void) +{ + delay_damage_ers = ers_new(sizeof(struct delay_damage),"battle.cpp::delay_damage_ers",ERS_OPT_CLEAR); + add_timer_func_list(battle_delay_damage_sub, "battle_delay_damage_sub"); +} + +/*================== + * end battle timer + *------------------*/ +void do_final_battle(void) +{ + ers_destroy(delay_damage_ers); +} diff --git a/src/bot/Brokk/battle.hpp b/src/bot/Brokk/battle.hpp new file mode 100644 index 0000000..54b4f66 --- /dev/null +++ b/src/bot/Brokk/battle.hpp @@ -0,0 +1,787 @@ +// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL +// For more information, see LICENCE in the main folder + +#ifndef BATTLE_HPP +#define BATTLE_HPP + +#include + +#include +#include +#include + +#include "map.hpp" //ELE_MAX +#include "skill.hpp" + +//fwd declaration +class map_session_data; +struct mob_data; +struct block_list; +enum e_damage_type : uint8; + + +/// State of a single attack attempt; used in flee/def penalty calculations when mobbed +enum damage_lv : uint8 { + 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 +}; + +/// Flag for base damage calculation +enum e_base_damage_flag : uint16 { + BDMG_NONE = 0x0000, /// None + BDMG_CRIT = 0x0001, /// Critical hit damage + BDMG_ARROW = 0x0002, /// Add arrow attack and use ranged damage formula + BDMG_MAGIC = 0x0004, /// Use MATK for base damage (e.g. Magic Crasher) + BDMG_NOSIZE = 0x0008, /// Skip target size adjustment (e.g. Weapon Perfection) +}; + +/// Flag of the final calculation +enum e_battle_flag : uint16 { + BF_NONE = 0x0000, /// None + BF_WEAPON = 0x0001, /// Weapon attack + BF_MAGIC = 0x0002, /// Magic attack + BF_MISC = 0x0004, /// Misc attack + + BF_SHORT = 0x0010, /// Short attack + BF_LONG = 0x0040, /// Long attack + + BF_SKILL = 0x0100, /// Skill attack + BF_NORMAL = 0x0200, /// Normal attack + + BF_WEAPONMASK = BF_WEAPON|BF_MAGIC|BF_MISC, /// Weapon attack mask + BF_RANGEMASK = BF_SHORT|BF_LONG, /// Range attack mask + BF_SKILLMASK = BF_SKILL|BF_NORMAL, /// Skill attack mask +}; + +/// Battle check target [Skotlex] +enum e_battle_check_target : uint32 { + BCT_NOONE = 0x000000, ///< No one + BCT_SELF = 0x010000, ///< Self + BCT_ENEMY = 0x020000, ///< Enemy + BCT_PARTY = 0x040000, ///< Party members + BCT_GUILDALLY = 0x080000, ///< Only allies, NOT guildmates + BCT_NEUTRAL = 0x100000, ///< Neutral target + BCT_SAMEGUILD = 0x200000, ///< Guildmates, No Guild Allies + + BCT_ALL = 0x3F0000, ///< All targets + + BCT_WOS = 0x400000, ///< Except self (currently used for skipping if src == bl in skill_area_sub) + BCT_GUILD = BCT_SAMEGUILD|BCT_GUILDALLY, ///< Guild AND Allies (BCT_SAMEGUILD|BCT_GUILDALLY) + BCT_NOGUILD = BCT_ALL&~BCT_GUILD, ///< Except guildmates + BCT_NOPARTY = BCT_ALL&~BCT_PARTY, ///< Except party members + BCT_NOENEMY = BCT_ALL&~BCT_ENEMY, ///< Except enemy + BCT_ALLY = BCT_PARTY|BCT_GUILD, + BCT_FRIEND = BCT_NOENEMY, +}; + +/// Check flag for common damage bonuses such as: ATKpercent, Refine, Passive Mastery, Spirit Spheres and Star Crumbs +enum e_bonus_chk_flag : uint8 { + BCHK_ALL, /// Check if all of the common damage bonuses apply to this skill + BCHK_REFINE, /// Check if refine bonus is applied (pre-renewal only currently) + BCHK_STAR, /// Check if Star Crumb bonus is applied (pre-renewal only currently) +}; + +/// Damage structure +struct Damage { +#ifdef RENEWAL + int64 statusAtk, statusAtk2, weaponAtk, weaponAtk2, equipAtk, equipAtk2, masteryAtk, masteryAtk2, percentAtk, percentAtk2; +#else + int64 basedamage; /// Right hand damage that a normal attack would deal +#endif + int64 damage, /// Right hand damage + damage2; /// Left hand damage + enum e_damage_type type; /// Check clif_damage for type + short div_; /// Number of hit + int amotion, + dmotion; + int blewcount; /// Number of knockback + int flag; /// chk e_battle_flag + int miscflag; + enum damage_lv dmg_lv; /// ATK_LUCKY,ATK_FLEE,ATK_DEF + bool isspdamage; /// Display blue damage numbers in clif_damage +}; + +// 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 flag); + +int64 battle_calc_return_damage(struct block_list *bl, struct block_list *src, int64 *, int flag, uint16 skill_id, bool status_reflect); + +void battle_drain(map_session_data *sd, struct block_list *tbl, int64 rdamage, int64 ldamage, int race, int class_); + +int64 battle_attr_fix(struct block_list* src, struct block_list* target, int64 damage, int atk_elem, int def_type, int def_lv, int flag = 0); +int battle_calc_cardfix(int attack_type, struct block_list *src, struct block_list *target, std::bitset nk, int s_ele, int s_ele_, int64 damage, int left, int flag); + +// Final calculation Damage +int64 battle_calc_damage(struct block_list *src,struct block_list *bl,struct Damage *d,int64 damage,uint16 skill_id,uint16 skill_lv); +int64 battle_calc_gvg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag); +int64 battle_calc_bg_damage(struct block_list *src,struct block_list *bl,int64 damage,uint16 skill_id,int flag); +int64 battle_calc_pk_damage(block_list &src, block_list &bl, int64 damage, uint16 skill_id, int flag); + +int battle_damage(struct block_list *src, struct block_list *target, int64 damage, t_tick delay, uint16 skill_lv, uint16 skill_id, enum damage_lv dmg_lv, unsigned short attack_type, bool additional_effects, t_tick tick, bool spdamage); +int battle_delay_damage (t_tick tick, int amotion, struct block_list *src, struct block_list *target, int attack_type, uint16 skill_id, uint16 skill_lv, int64 damage, enum damage_lv dmg_lv, t_tick ddelay, bool additional_effects, bool spdamage); +int battle_fix_damage(struct block_list* src, struct block_list* target, int64 damage, t_tick walkdelay, uint16 skill_id); + +int battle_calc_chorusbonus(map_session_data *sd); + +// Summary normal attack treatment (basic attack) +enum damage_lv battle_weapon_attack( struct block_list *bl,struct block_list *target,t_tick 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); +uint16 battle_getcurrentskill(struct block_list *bl); + +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); +bool battle_check_coma(map_session_data& sd, struct block_list& target, e_battle_flag attack_type); + +void battle_consume_ammo(map_session_data* sd, int skill, int lv); + +bool is_infinite_defense(struct block_list *target, int flag); + +// 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 +#define MIN_BODY_STYLE battle_config.min_body_style +#define MAX_BODY_STYLE battle_config.max_body_style + +struct Battle_Config +{ + int warp_point_debug; + int enable_critical; + int mob_critical_rate; + int critical_rate; + int enable_baseatk, enable_baseatk_renewal; + 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 cardfix_monster_physical; + 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 guild_skill_relog_type; + 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_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 pet_unequip_destroy; + int pet_master_dead; + + 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 hanbok_ignorepalette; + int oktoberfest_ignorepalette; + int natural_healhp_interval; + int natural_healsp_interval; + int natural_heal_skill_interval; + int natural_heal_weight_rate; + int natural_heal_weight_rate_renewal; + int arrow_decrement; + int ammo_unequip; + int ammo_check_weapon; + int max_aspd; + int max_walk_speed; //Maximum walking speed after buffs [Skotlex] + int max_hp_lv99; + int max_hp_lv150; + 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 vending_tax_min; + 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 item_rate_common_mvp, item_rate_heal_mvp, item_rate_use_mvp, item_rate_equip_mvp, item_rate_card_mvp; + + 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_mvp_mode; //rAthena addition [Playtester] + 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 prevent_logout_trigger; + int land_protector_behavior; + int npc_emotion_behavior; + + 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 multi_level_up_base; + int multi_level_up_job; + int max_exp_gain_rate; //Max amount of exp bar % you can get in one go. + int pk_mode; + int pk_mode_mes; + 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 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 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 skill_steal_random_options; + 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 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 drop_rate_cap; // Drop rate can't be raised above this amount by drop bonus items + int drop_rate_cap_vip; + + 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 official_cell_stack_limit; // [Playtester] + int custom_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_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 knockback_left; + 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; + int hom_S_growth_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 max_trans_parameter; + int max_third_trans_parameter; + int max_extended_parameter; + int max_summoner_parameter; + int max_fourth_parameter; + int max_third_aspd; + int max_summoner_aspd; + int vcast_stat_scale; + + int mvp_tomb_enabled; + int mvp_tomb_delay; + + int atcommand_suggestions_enabled; + int min_npc_vendchat_distance; + int atcommand_mobinfo_type; + + int mob_size_influence; // Enable modifications on earned experience, drop rates and monster status depending on monster size. [mkbu95] + int skill_trap_type; + int allow_consume_restricted_item; + int allow_equip_restricted_item; + int max_walk_path; + int item_enabled_npc; + int item_onfloor; // Whether to drop an undroppable item on the map or destroy it if inventory is full. + int bowling_bash_area; + int drop_rateincrease; + int feature_auction; + int feature_banking; + int vip_storage_increase; + int vip_base_exp_increase; + int vip_job_exp_increase; + int vip_zeny_penalty; + int vip_bm_increase; + int vip_drop_increase; + int vip_gemstone; + int vip_exp_penalty_base; + int vip_exp_penalty_job; + int vip_disp_rate; + int mon_trans_disable_in_gvg; + int discount_item_point_shop; + int update_enemy_position; + int devotion_rdamage; + int feature_itemlink; + int feature_mesitemlink; + int feature_mesitemlink_brackets; + int feature_mesitemlink_dbname; + + // autotrade persistency + int feature_autotrade; + int feature_autotrade_direction; + int feature_autotrade_head_direction; + int feature_autotrade_sit; + int feature_autotrade_open_delay; + + // Fame points + int fame_taekwon_mission; + int fame_refine_lv1; + int fame_refine_lv2; + int fame_refine_lv3; + int fame_forge; + int fame_pharmacy_3; + int fame_pharmacy_5; + int fame_pharmacy_7; + int fame_pharmacy_10; + + int disp_servervip_msg; + int warg_can_falcon; + int path_blown_halt; + int rental_mount_speed_boost; + int warp_suggestions_enabled; + int taekwon_mission_mobname; + int teleport_on_portal; + int cart_revo_knockback; + int guild_notice_changemap; + int transcendent_status_points; + int taekwon_ranker_min_lv; + int revive_onwarp; + int mail_delay; + int autotrade_monsterignore; + int idletime_option; + int spawn_direction; + int arrow_shower_knockback; + int devotion_rdamage_skill_only; + int max_extended_aspd; + int mob_chase_refresh; //How often a monster should refresh its chase [Playtester] + int mob_icewall_walk_block; //How a normal monster should be trapped in icewall [Playtester] + int boss_icewall_walk_block; //How a boss monster should be trapped in icewall [Playtester] + int snap_dodge; // Enable or disable dodging damage snapping away [csnv] + int stormgust_knockback; + int default_fixed_castrate; + int default_bind_on_equip; + int pet_ignore_infinite_def; // Makes fixed damage of petskillattack2 ignores infinite defense + int homunculus_evo_intimacy_need; + int homunculus_evo_intimacy_reset; + int monster_loot_search_type; + int feature_roulette; + int feature_roulette_bonus_reward; + int monster_hp_bars_info; + int min_body_style; + int max_body_style; + int save_body_style; + int mob_eye_range_bonus; //Vulture's Eye and Snake's Eye range bonus + int mob_stuck_warning; //Show warning if a monster is stuck too long + int skill_eightpath_algorithm; //Official path algorithm + int skill_eightpath_same_cell; + int death_penalty_maxlv; + int exp_cost_redemptio; + int exp_cost_redemptio_limit; + int mvp_exp_reward_message; + int can_damage_skill; //Which BL types can damage traps + int atcommand_levelup_events; + int atcommand_disable_npc; + int block_account_in_same_party; + int tarotcard_equal_chance; //Official or equal chance for each card + int change_party_leader_samemap; + int dispel_song; //Can songs be dispelled? + int guild_maprespawn_clones; // Should clones be killed by maprespawnguildid? + int hide_fav_sell; + int mail_daily_count; + int mail_zeny_fee; + int mail_attachment_price; + int mail_attachment_weight; + int banana_bomb_duration; + int guild_leaderchange_delay; + int guild_leaderchange_woe; + int guild_alliance_onlygm; + int feature_achievement; + int allow_bound_sell; + int autoloot_adjust; + int feature_petevolution; + int feature_pet_autofeed; + int feature_pet_autofeed_rate; + int pet_autofeed_always; + int broadcast_hide_name; + int skill_drop_items_full; + int switch_remove_edp; + int feature_homunculus_autofeed; + int feature_homunculus_autofeed_rate; + int summoner_race; + int summoner_size; + int homunculus_autofeed_always; + int feature_attendance; + int feature_privateairship; + int rental_transaction; + int min_shop_buy; + int min_shop_sell; + int feature_equipswitch; + int pet_walk_speed; + int blacksmith_fame_refine_threshold; + int mob_nopc_idleskill_rate; + int mob_nopc_move_rate; + int boss_nopc_idleskill_rate; + int boss_nopc_move_rate; + int hom_idle_no_share; + int idletime_hom_option; + int devotion_standup_fix; + int feature_bgqueue; + int bgqueue_nowarp_mapflag; + int homunculus_exp_gain; + int rental_item_novalue; + int ping_timer_interval; + int ping_time; + int show_skill_scale; + int achievement_mob_share; + int slave_stick_with_master; + int at_logout_event; + int homunculus_starving_rate; + int homunculus_starving_delay; + int drop_connection_on_quit; + int mob_spawn_variance; + int mercenary_autoloot; + int mer_idle_no_share; + int idletime_mer_option; + int feature_refineui; + int rndopt_drop_pillar; + int pet_legacy_formula; + int pet_distance_check; + int pet_hide_check; + + int instance_block_leave; + int instance_block_leaderchange; + int instance_block_invite; + int instance_block_expulsion; + // 4th Jobs Stuff + int trait_points_job_change; + int use_traitpoint_table; + int max_trait_parameter; + int max_res_mres_ignored; + int max_ap; + int ap_rate; + int restart_ap_rate; + int loose_ap_on_death; + int loose_ap_on_map; + int keep_ap_on_logout; + int attack_machine_level_difference; + + int feature_barter; + int feature_barter_extended; + int break_mob_equip; + int macro_detection_retry; + int macro_detection_timeout; + int macro_detection_punishment; + int macro_detection_punishment_time; + + int feature_dynamicnpc_timeout; + int feature_dynamicnpc_rangex; + int feature_dynamicnpc_rangey; + int feature_dynamicnpc_direction; + + int mob_respawn_time; + int mob_unlock_time; + int map_edge_size; + int randomize_center_cell; + + int feature_stylist; + int feature_banking_state_enforce; + int instance_allow_reconnect; + int synchronize_damage; + int item_stacking; + int hom_delay_reset_vaporize; + int hom_delay_reset_warp; + +#include +}; + +extern struct Battle_Config battle_config; + +void do_init_battle(void); +void do_final_battle(void); +extern int battle_config_read(const char *cfgName); +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_HPP */ diff --git a/src/bot/Brokk/bot_account.cpp b/src/bot/Brokk/bot_account.cpp new file mode 100644 index 0000000..419eb9e --- /dev/null +++ b/src/bot/Brokk/bot_account.cpp @@ -0,0 +1,825 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + + +#include "bot_account.hpp" + +#include //min / max +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "brokk.hpp" // brokk_config + +/// global defines + +/// internal structure +typedef struct BotAccountDB_SQL { + BotAccountDB vtable; // public interface + Sql* bot_accounts; // SQL handle bot accounts storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = "ragnarok"; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char bot_account_db[32]; + char login_account_db[32]; + char global_acc_reg_num_table[32]; + char global_acc_reg_str_table[32]; + +} BotAccountDB_SQL; + +/// internal structure +typedef struct BotAccountDBIterator_SQL { + BotAccountDBIterator vtable; // public interface + BotAccountDB_SQL* db; + int last_bot_account_id; +} BotAccountDBIterator_SQL; + +/// internal functions +static bool bot_account_db_sql_init(BotAccountDB* self); +static void bot_account_db_sql_destroy(BotAccountDB* self); +static bool bot_account_db_sql_get_property(BotAccountDB* self, const char* key, char* buf, size_t buflen); +static bool bot_account_db_sql_set_property(BotAccountDB* self, const char* option, const char* value); +static bool bot_account_db_sql_create(BotAccountDB* self, struct mmo_bot_account* bot_acc); +static bool bot_account_db_sql_remove(BotAccountDB* self, const uint32 account_id); +static bool bot_account_db_sql_save(BotAccountDB* self, const struct mmo_bot_account* bot_acc, bool refresh_token); +static bool bot_account_db_sql_load_num(BotAccountDB* self, struct mmo_bot_account* bot_acc, const uint32 account_id); +static bool bot_account_db_sql_load_str(BotAccountDB* self, struct mmo_bot_account* bot_acc, const char* creator_name); +static BotAccountDBIterator* bot_account_db_sql_iterator(BotAccountDB* self); +static void bot_account_db_sql_iter_destroy(BotAccountDBIterator* self); +static bool bot_account_db_sql_iter_next(BotAccountDBIterator* self, struct mmo_bot_account* bot_acc); +static bool mmo_bot_auth_fromsql(BotAccountDB_SQL* db, struct mmo_bot_account* bot_acc, uint32 account_id); +static bool mmo_bot_auth_tosql(BotAccountDB_SQL* db, const struct mmo_bot_account* bot_acc, bool is_new, bool refresh_token); + +/// public constructor +BotAccountDB* bot_account_db_sql(void) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)aCalloc(1, sizeof(BotAccountDB_SQL)); + new(db) BotAccountDB_SQL(); + + // set up the vtable + db->vtable.init = &bot_account_db_sql_init; + db->vtable.destroy = &bot_account_db_sql_destroy; + db->vtable.get_property = &bot_account_db_sql_get_property; + db->vtable.set_property = &bot_account_db_sql_set_property; + db->vtable.save = &bot_account_db_sql_save; + db->vtable.create = &bot_account_db_sql_create; + db->vtable.remove = &bot_account_db_sql_remove; + db->vtable.load_num = &bot_account_db_sql_load_num; + db->vtable.load_str = &bot_account_db_sql_load_str; + db->vtable.iterator = &bot_account_db_sql_iterator; + + // initialize to default values + db->bot_accounts = nullptr; + + // other settings + db->case_sensitive = false; + safestrncpy(db->bot_account_db, "bot_accounts", sizeof(db->bot_account_db)); + safestrncpy(db->login_account_db, "login", sizeof(db->login_account_db)); + //safestrncpy(db->global_acc_reg_num_table, "global_acc_reg_num", sizeof(db->global_acc_reg_num_table)); + //safestrncpy(db->global_acc_reg_str_table, "global_acc_reg_str", sizeof(db->global_acc_reg_str_table)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool bot_account_db_sql_init(BotAccountDB* self) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle; + + db->bot_accounts = Sql_Malloc(); + sql_handle = db->bot_accounts; + + if (SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str())) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->bot_accounts); + db->bot_accounts = nullptr; + return false; + } + + if (!db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str())) + Sql_ShowDebug(sql_handle); + + //self->remove_webtokens( self ); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void bot_account_db_sql_destroy(BotAccountDB* self){ + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + + //if( SQL_ERROR == Sql_Query( db->bot_accounts, "UPDATE `%s` SET `web_auth_token` = NULL", db->bot_account_db ) ){ + // Sql_ShowDebug( db->accounts ); + //} + + Sql_Free(db->bot_accounts); + db->bot_accounts = nullptr; + + db->~BotAccountDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_get_property(BotAccountDB* self, const char* key, char* buf, size_t buflen) +{ + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "bot_account_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->bot_account_db); + //else + //if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + // safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_table); + //else + //if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + // safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_table); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_set_property(BotAccountDB* self, const char* key, const char* value) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "bot_account_db") == 0 ) + safestrncpy(db->bot_account_db, value, sizeof(db->bot_account_db)); + else + if (strcmpi(key, "login_account_db") == 0) + safestrncpy(db->login_account_db, value, sizeof(db->login_account_db)); + //else + //if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + // safestrncpy(db->global_acc_reg_str_table, value, sizeof(db->global_acc_reg_str_table)); + //else + //if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + // safestrncpy(db->global_acc_reg_num_table, value, sizeof(db->global_acc_reg_num_table)); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Create a new account entry. + * acc->account_id = -1 means auto_decidet by server + * bot_acc->account_id is always matching its login table counter part, + * and its value will be written to bot_acc->account_id if everything succeeds. + * @param self: pointer to db + * @param acc: pointer of mmo_bot_account to save + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_create(BotAccountDB* self, struct mmo_bot_account* bot_acc) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle_bot = db->bot_accounts; + + // validate account id to assign + uint32 account_id; + account_id = bot_acc->account_id; + + // 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 + bot_acc->account_id = account_id; + return mmo_bot_auth_tosql(db, bot_acc, true, false); +} + +/** + * Delete an existing bot account entry and its regs. + * @param self: pointer to db + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_remove(BotAccountDB* self, const uint32 account_id) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle = db->bot_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->bot_account_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 bot_account with the new data provided. + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to save + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_save(BotAccountDB* self, const struct mmo_bot_account* bot_acc, bool refresh_token) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + return mmo_bot_auth_tosql(db, bot_acc, false, refresh_token); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to mmo_bot_auth_fromsql. + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to fill + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_load_num(BotAccountDB* self, struct mmo_bot_account* bot_acc, const uint32 account_id) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + return mmo_bot_auth_fromsql(db, bot_acc, account_id); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Doesn't actually retrieve data yet: escapes and checks userid, then transforms it to accid for fetching. + * Filled data structure is done by delegation to bot_account_db_sql_load_num. + * This function basicly fetches account_ids of all bots of an account + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to fill + * @param userid: name of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_load_str(BotAccountDB* self, struct mmo_bot_account* bot_acc, const char* userid) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle = db->bot_accounts; + char esc_userid[2*NAME_LENGTH+1]; + uint32 account_id; + char* data; + + Sql_EscapeString(sql_handle, esc_userid, userid); + + // get the list of account IDs for this userid + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'", + db->login_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("bot_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; + } + + //TODO FIXME this will only get the first found bot to a creator (Currently not a problem, because only one bot per account) + Sql_GetData(sql_handle, 0, &data, nullptr); + account_id = atoi(data); + + return bot_account_db_sql_load_num(self, bot_acc, account_id); +} + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static BotAccountDBIterator* bot_account_db_sql_iterator(BotAccountDB* self) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)aCalloc(1, sizeof(BotAccountDBIterator_SQL)); + + // set up the vtable + iter->vtable.destroy = &bot_account_db_sql_iter_destroy; + iter->vtable.next = &bot_account_db_sql_iter_next; + + // fill data + iter->db = db; + iter->last_bot_account_id = -1; + + return &iter->vtable; +} + +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void bot_account_db_sql_iter_destroy(BotAccountDBIterator* self) { + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next account in the database. + * @param self: pointer to db iterator + * @param bot_acc: pointer of mmo_bot_account to fill + * @return true if next account found and filled, false if something has failed + */ +static bool bot_account_db_sql_iter_next(BotAccountDBIterator* self, struct mmo_bot_account* bot_acc) { + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)self; + BotAccountDB_SQL* db = iter->db; + Sql* sql_handle = db->bot_accounts; + 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->bot_account_db, iter->last_bot_account_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) && + SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) && + data != nullptr ) + {// get account data + uint32 account_id; + account_id = atoi(data); + if( mmo_bot_auth_fromsql(db, bot_acc, account_id) ) + { + iter->last_bot_account_id = account_id; + Sql_FreeResult(sql_handle); + return true; + } + } + Sql_FreeResult(sql_handle); + return false; +} + +/** + * Fetch a struct mmo_bot_account from sql. + * @param db: pointer to db + * @param acc: pointer of mmo_bot_account to fill + * @param account_id: id of user account to take data from + * @return true if successful, false if something has failed + */ +static bool mmo_bot_auth_fromsql(BotAccountDB_SQL* db, struct mmo_bot_account* bot_acc, uint32 account_id) { + Sql* sql_handle = db->bot_accounts; + char* data; + + // retrieve login entry for the specified account + if( SQL_ERROR == Sql_Query(sql_handle, +#ifdef VIP_ENABLE + "SELECT `account_id`,`is_bot`,`creator_id`,`creator_name` FROM `%s` WHERE `account_id` = %d", +#else + "SELECT `account_id`,`is_bot`,`creator_id`,`creator_name` FROM `%s` WHERE `account_id` = %d", +#endif + db->bot_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, nullptr); bot_acc->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, nullptr); bot_acc->is_bot = (uint8)atoi(data); + Sql_GetData(sql_handle, 2, &data, nullptr); bot_acc->creator_id = atoi(data); + Sql_GetData(sql_handle, 3, &data, nullptr); safestrncpy(bot_acc->creator_name, data, sizeof(bot_acc->creator_name)); +//ifdef VIP_ENABLE + +//endif + Sql_FreeResult(sql_handle); + + return true; +} + +/** + * Save a struct mmo_bot_account in sql. + * @param db: pointer to db + * @param bot_acc: pointer of mmo_account to save + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool mmo_bot_auth_tosql(BotAccountDB_SQL* db, const struct mmo_bot_account* bot_acc, bool is_new, bool refresh_token) { + Sql* sql_handle = db->bot_accounts; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // 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, +#ifdef VIP_ENABLE + "INSERT INTO `%s` (`account_id`, `is_bot`, `creator_id`, `creator_name`) VALUES (?, ?, ?, ?)", +#else + "INSERT INTO `%s` (`account_id`, `is_bot`, `creator_id`, `creator_name`) VALUES (?, ?, ?, ?)", +#endif + db->bot_account_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)&bot_acc->account_id, sizeof(bot_acc->account_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_SHORT, (void*)&bot_acc->is_bot, sizeof(bot_acc->is_bot)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, (void*)&bot_acc->creator_id, sizeof(bot_acc->creator_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void*)&bot_acc->creator_name, strlen(bot_acc->creator_name)) +//ifdef VIP_ENABLE +//endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + {// update is bot state (meaning if controlled by sindri or by other means) + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "UPDATE `%s` SET `is_bot`=? WHERE `account_id` = '%d'", +#else + "UPDATE `%s` SET `is_bot`=? WHERE `account_id` = '%d'", +#endif + db->bot_account_db, bot_acc->account_id) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)bot_acc->is_bot, sizeof(bot_acc->is_bot)) +#ifdef VIP_ENABLE +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + 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; +} + +/* +void mmo_save_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + uint16 count = RFIFOW(fd, 12); + + if (count) { + int cursor = 14, i; + char key[32], sval[254], esc_key[32*2+1], esc_sval[254*2+1]; + + for (i = 0; i < count; i++) { + uint32 index; + safestrncpy(key, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + Sql_EscapeString(sql_handle, esc_key, key); + cursor += RFIFOB(fd, cursor) + 1; + + index = RFIFOL(fd, cursor); + cursor += 4; + + switch (RFIFOB(fd, cursor++)) { + // int + case 0: + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%" PRId64 "')", db->global_acc_reg_num_table, account_id, esc_key, index, RFIFOQ(fd, cursor)) ) + Sql_ShowDebug(sql_handle); + cursor += 8; + break; + case 1: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_num_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + // str + case 2: + safestrncpy(sval, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + cursor += RFIFOB(fd, cursor) + 1; + Sql_EscapeString(sql_handle, esc_sval, sval); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%s')", db->global_acc_reg_str_table, account_id, esc_key, index, esc_sval) ) + Sql_ShowDebug(sql_handle); + break; + case 3: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_str_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + default: + ShowError("mmo_save_global_accreg: unknown type %d\n",RFIFOB(fd, cursor - 1)); + return; + } + } + } +}*/ +/* +void mmo_send_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + char* data; + int16 plen = 0; + size_t len; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_str_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + // + // * Vessel! + // * + // * str type + // * { keyLength(B), key(), index(L), valLength(B), val() } + // + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + } + } + + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_num_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + // + // * Vessel! + // * + // * int type + // * { keyLength(B), key(), index(L), value(L) } + // + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + + WFIFOQ(fd, plen) = strtoll(data,nullptr,10); + plen += 8; + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + + plen = 16; + } + } + + WFIFOB(fd, 12) = 1; + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); +}*/ + +/* +bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '1' WHERE `account_id` = '%u'", db->account_db, account_id ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +}*/ + +/** + * Timered function to disable webtoken for user + * If the user is online, then they must have logged since we started the timer. + * In that case, do nothing. The new authtoken must be valid. + * @param tid: timer id + * @param tick: tick of execution + * @param id: user account id + * @param data: BotAccountDB // because we don't use singleton??? + * @return :0 + +TIMER_FUNC(account_disable_webtoken_timer){ + const struct online_login_data* p = login_get_online_user(id); + AccountDB_SQL* db = reinterpret_cast(data); + + if (p == nullptr) { + ShowInfo("Web Auth Token for account %d was disabled\n", id); + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '0' WHERE `account_id` = '%u'", db->account_db, id ) ){ + Sql_ShowDebug( db->accounts ); + return 0; + } + } + + return 0; +} +*/ + +/* +bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + //add_timer(gettick() + brokk_config.disable_webtoken_delay, account_disable_webtoken_timer, account_id, reinterpret_cast(db)); + + return true; +}*/ +/* +bool account_db_sql_remove_webtokens( AccountDB* self ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL, `web_auth_token_enabled` = '0'", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +}*/ diff --git a/src/bot/Brokk/bot_account.hpp b/src/bot/Brokk/bot_account.hpp new file mode 100644 index 0000000..184a941 --- /dev/null +++ b/src/bot/Brokk/bot_account.hpp @@ -0,0 +1,133 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef BOT_ACCOUNT_HPP +#define BOT_ACCOUNT_HPP + +#include +#include // ACCOUNT_REG2_NUM, WEB_AUTH_TOKEN_LENGTH +#include + +typedef struct BotAccountDB BotAccountDB; +typedef struct BotAccountDBIterator BotAccountDBIterator; + + +// standard engines +BotAccountDB* bot_account_db_sql(void); + +struct mmo_bot_account { + uint32 account_id; // ID of corresponding login account + int is_bot; // Flag for if account is declared bot (will be controllable by sindri) + uint32 creator_id; // ID of login account of creating/summoning player [Default: 0 for config generated bots) + char creator_name[NAME_LENGTH]; // Name of the player character that spawned/summoned the bot [Default: Brokk for generated bots) +}; + + +struct BotAccountDBIterator { + /// Destroys this iterator, releasing all allocated memory (including itself). + /// + /// @param self Iterator + void (*destroy)(BotAccountDBIterator* 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)(BotAccountDBIterator* self, struct mmo_bot_account* bot_acc); +}; + + +struct BotAccountDB { + /// Initializes this database, making it ready for use. + /// Call this after setting the properties. + /// + /// @param self Database + /// @return true if successful + bool (*init)(BotAccountDB* self); + + /// Destroys this database, releasing all allocated memory (including itself). + /// + /// @param self Database + void (*destroy)(BotAccountDB* self); + + /// Gets a property from this database. + /// These read-only properties must be implemented: + /// + /// @param self Database + /// @param key Property name + /// @param buf Buffer for the value + /// @param buflen Buffer length + /// @return true if successful + bool (*get_property)(BotAccountDB* 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)(BotAccountDB* self, const char* key, const char* value); + + /// Creates a new account in this database. + /// bot_acc->account_id must always be set, because there will never be a valid bot_account without login-table account + /// + /// @param self Database + /// @param acc Account data + /// @return true if successful + bool (*create)(BotAccountDB* self, struct mmo_bot_account* bot_acc); + + /// Removes an account from this database. + /// + /// @param self Database + /// @param account_id Account id + /// @return true if successful + bool (*remove)(BotAccountDB* self, const uint32 account_id); + + /// Enables the web auth token for the given account id + //bool (*enable_webtoken)(BotAccountDB* self, const uint32 account_id); + + /// Disables the web auth token for the given account id + //bool (*disable_webtoken)(BotAccountDB* self, const uint32 account_id); + + /// Removes the web auth token for all accounts + //bool (*remove_webtokens)(BotAccountDB* self); + + /// Modifies the data of an existing account. + /// Uses bot_acc->account_id to identify the account. + /// + /// @param self Database + /// @param bot_acc Account data + /// @param refresh_token Whether or not to refresh the web auth token + /// @return true if successful + bool (*save)(BotAccountDB* self, const struct mmo_bot_account* bot_acc, bool refresh_token); + + /// Finds an bot_account with account_id and copies it to bot_acc. + /// + /// @param self Database + /// @param bot_acc Pointer that receives the account data + /// @param account_id Target account id + /// @return true if successful + bool (*load_num)(BotAccountDB* self, struct mmo_bot_account* bot_acc, const uint32 account_id); + + /// Finds an bot_account with creator_name and copies it to bot_acc. + /// + /// @param self Database + /// @param bot_acc Pointer that receives the account data + /// @param userid Target username + /// @return true if successful + bool (*load_str)(BotAccountDB* self, struct mmo_bot_account* bot_acc, const char* userid); + + /// Returns a new forward iterator. + /// + /// @param self Database + /// @return Iterator + BotAccountDBIterator* (*iterator)(BotAccountDB* self); +}; + +//void mmo_send_global_accreg(BotAccountDB* self, int fd, uint32 account_id, uint32 char_id); +//void mmo_save_global_accreg(BotAccountDB* self, int fd, uint32 account_id, uint32 char_id); + +#endif /* BOT_ACCOUNT_HPP */ diff --git a/src/bot/Brokk/brokk.cpp b/src/bot/Brokk/brokk.cpp index b0e6062..cabe602 100644 --- a/src/bot/Brokk/brokk.cpp +++ b/src/bot/Brokk/brokk.cpp @@ -6,7 +6,7 @@ #pragma warning(disable:4800) #include "brokk.hpp" -#include +//#include #include #include #include @@ -16,17 +16,753 @@ #include #include //ip2str #include +#include #include #include #include #include +#include +#include // for file handling +#include +#include +#include +#include +#include + +#include +#include "pc.hpp" +#include "account.hpp" +#include "bot_account.hpp" +#include "charakters.hpp" +#include "json.hpp" // The header-only library for JSON parsing +//helpers (Store adepted functions from different areas in rathena +//#include "config_helpers.hpp" +#include "char_creation_helpers.hpp" +#include "helper_skills.hpp" using namespace rathena; using namespace rathena::server_brokk; +using json = nlohmann::json; -void BrokkServer::finalize() { +struct Brokk_Config brokk_config; /// Configuration of brokk-server + +// DB Object of all touched infrastructure +AccountDB* login_accounts = nullptr; +BotAccountDB* bot_accounts = nullptr; +CharDB* charakters = nullptr; +SkillDB* skills = nullptr; + +// YAML DB Objects of all touched infrastucture +BotStatPointDatabase bot_statpoint_db; +BotJobDatabase bot_job_db; +//SkillTreeDatabase skill_tree_db; + +// Advanced subnet check [LuzZza] +struct s_subnet { + uint32 mask; + uint32 char_ip; + uint32 map_ip; +} subnet[16]; +int subnet_count = 0; //number of subnet config + +int brokk_fd; // brokk server file descriptor socket + +/** +* Retrieves if account_id is online + +struct online_login_data* login_get_online_user(uint32 account_id) { + return util::umap_find(online_db, account_id); +} +*/ + +TIMER_FUNC(periodic_cleanup_timer) { + bot_account_cleanup(); // Call the cleanup function + return 0; // Return 0 to keep the timer going +} + +void setup_periodic_cleanup_timer() { + // Set up a timer to run the cleanup function every 15 minutes (900,000 milliseconds) + add_timer_interval(gettick() + 900000, periodic_cleanup_timer, 0, 0, 900000); +} + + + +/** + * Create a new bot account and save it in db/sql. + * @param userid: string for bot login + * @param pass: string for bot password + * @param sex: should be M|F|S + * @param last_ip: the IP address to associate with the bot + * @return : + * -1: success + * 0: unregistered id (invalid sex or failure to create in db) + * 1: incorrect pass or userid (userid/pass too short or already exists) + * 2: Error while writing into bot_accounts table + * 3: Registration denied + */ +int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip, const bool auto_generated, int creator_id = 0, const char* creator_name = "Brokk") { + static int num_regs = 0; // bot registration counter + static t_tick new_reg_tick = 0; + t_tick tick = gettick(); + struct mmo_account acc; + struct mmo_bot_account bot_acc; + + // Bot account registration flood protection + if (new_reg_tick == 0) + new_reg_tick = gettick(); + if (auto_generated == false) { + if (DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= brokk_config.allowed_bot_creations) { + ShowNotice("Bot account registration denied (registration limit exceeded)\n"); + return 3; + } + } + // Validate user ID and password length + if (strlen(userid) < brokk_config.acc_name_min_length || strlen(pass) < brokk_config.password_min_length) + return 1; + + // Ensure valid sex input (M/F for bots) + if (sex != 'M' && sex != 'F') + return 0; // Unregistered ID + + // Check if the bot account already exists + if (login_accounts->load_str(login_accounts, &acc, userid)) { + ShowNotice("Attempt to create an already existing bot account (account: %s, sex: %c)\n", userid, sex); + return 1; // Account exists + } + + // First create main login account + // Clear and populate account structure + 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, "bot@brokk.ai", sizeof(acc.email)); // Default email for bots + acc.expiration_time = 0; // Bots don't expire + safestrncpy(acc.lastlogin, "", sizeof(acc.lastlogin)); + safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip)); + safestrncpy(acc.birthdate, "", sizeof(acc.birthdate)); + safestrncpy(acc.pincode, "", sizeof(acc.pincode)); + acc.pincode_change = 0; + acc.char_slots = MIN_CHARS; + +#ifdef VIP_ENABLE + acc.vip_time = 0; + acc.old_group = 0; +#endif + + // Create the account in the database + if (!login_accounts->create(login_accounts, &acc)) + return 0; // Account creation failed + + // Second create bot account entry + + // Check if the bot account already exists + if (bot_accounts->load_num(bot_accounts, &bot_acc, acc.account_id)) { + ShowNotice("Attempt to create an already existing bot account (account: %s, sex: %c)\n", acc.account_id, sex); + return 1; // Account exists + } + + // Clear and populate account structure + memset(&bot_acc, '\0', sizeof(bot_acc)); + bot_acc.account_id = acc.account_id; // Assigned by account db + bot_acc.is_bot = 1; + bot_acc.creator_id = creator_id; // Assigned by account db + safestrncpy(bot_acc.creator_name, creator_name, sizeof(bot_acc.creator_name)); + +//ifdef VIP_ENABLE + +//endif + + // Create the account in the database + if (!bot_accounts->create(bot_accounts, &bot_acc)) + return 0; // Account creation failed + + ShowNotice("Bot account creation successful (account %s, id: %d, sex: %c)\n", acc.userid, acc.account_id, acc.sex); + + // Registration rate limit logic + if (DIFF_TICK(tick, new_reg_tick) > 0) { + num_regs = 0; + new_reg_tick = tick + brokk_config.time_allowed * 1000; + } + ++num_regs; + + return -1; // Success +} + +/** + * Delete a bot account from db/sql by userid. + * @param userid: string for bot login + * @return : + * 0: success + * 1: failure (account does not exist) + */ +int brokk_mmo_auth_delete_str(const char* userid) { + struct mmo_account acc; + struct mmo_bot_account bot_acc; + + // Check if the account exists + if (!login_accounts->load_str(login_accounts, &acc, userid)) { + ShowNotice("Attempt to delete non-existent account (account: %s)\n", userid); + return 1; // Account does not exist + } + + // Use loaded acc to remove bot(s) + //TODO FIXME: Potential error when is_bot is disabled, deletion of account on other ways then by brokk + // could result in orphaned bot_account entries-> needs check + if (!bot_accounts->load_num(bot_accounts, &bot_acc, acc.account_id)) { + ShowNotice("Attempt to delete non_exiting bot account (accid: %s)\n", acc.account_id); + return 1; // Bot account does not exist + } + + // Delete login account and corresponding bot account + if (!login_accounts->remove(login_accounts, acc.account_id)) { + ShowError("Failed to delete login account (account: %s)\n", userid); + return 1; // Deletion failed + } + if (!bot_accounts->remove(bot_accounts, bot_acc.account_id)) { + ShowError("Failed to delete bot account (account: %s)\n", userid); + return 1; // Deletion failed + } + + ShowNotice("Bot account deleted successfully (account: %s, id: %d)\n", acc.userid, acc.account_id); + return 0; // Success +} + +/** + * Delete a bot account from db/sql by accid. + * @param acccount_id: int + * @return : + * 0: success + * 1: failure (account does not exist) + */ +int brokk_mmo_auth_delete_num(uint32 account_id) { + struct mmo_account acc; + struct mmo_bot_account bot_acc; + struct mmo_charakter charakter; + std::vector char_ids_to_delete; + + // Check if the account exists + if (!login_accounts->load_num(login_accounts, &acc, account_id)) { + ShowNotice("Attempt to delete non-existent account (account_id: %d)\n", account_id); + return 1; // Account does not exist + } + + // Use loaded acc to remove bot(s) + //TODO FIXME: Potential error when is_bot is disabled, deletion of account on other ways then by brokk + // could result in orphaned bot_account entries-> needs check + if (!bot_accounts->load_num(bot_accounts, &bot_acc, account_id)) { + ShowNotice("Attempt to delete non_exiting bot account (accid: %d)\n", account_id); + return 1; // Bot account does not exist + } + + // Delete login account and corresponding bot account + if (!login_accounts->remove(login_accounts, acc.account_id)) { + ShowError("Failed to delete login account (account_id: %d)\n", account_id); + return 1; // Deletion failed + } + if (!bot_accounts->remove(bot_accounts, bot_acc.account_id)) { + ShowError("Failed to delete bot account (account_id: %d)\n", account_id); + return 1; // Deletion failed + } + + //Attempt to delete charakters of the bot (if any) + // First pass: Collect char_ids to delete based on config + + CharakterDBIterator* iter_chars = charakters->iterator(charakters); // Create the iterator + while (iter_chars->next(iter_chars, &charakter)) { + //ShowDebug("Charakter %s scanned\n", charakter.name); + + if (charakter.account_id == account_id) { + //ShowInfo("Charakter %s marked for deletion\n", charakter.name); + char_ids_to_delete.push_back(charakter.char_id); + } + } + iter_chars->destroy(iter_chars); // Destroy the iterator after use + + // Second pass: Delete the collected charakters + for (const auto& char_id : char_ids_to_delete) { + ShowInfo("Deleting charakter with ID %d.\n", char_id); + if (!charakters->remove(charakters, char_id)) { + ShowError("Failed to delete charakter (char_id: %s)\n", char_id); + continue; // Deletion failed + } + } + + ShowNotice("Bot account deletion completed (account: %s, id: %d)\n\n", acc.userid, acc.account_id); + return 0; // Success +} + +/// Set and read Configurations + +/** + * Reading Lan Support configuration. + * @param lancfgName: Name of the lan configuration (could be fullpath) + * @return 0:success, 1:failure (file not found|readable) + +int brokk_lan_config_read(const char* lancfgName) { + FILE* fp; + int line_num = 0, s_subnet = ARRAYLENGTH(subnet); + char line[1024], w1[64], w2[64], w3[64], w4[64]; + + if ((fp = fopen(lancfgName, "r")) == nullptr) { + 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, "%63[^:]: %63[^:]:%63[^:]:%63[^\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) { + if (subnet_count >= s_subnet) { //We skip instead of break in case we want to add other conf in that file. + ShowError("%s: Too many subnets defined, skipping line %d...\n", lancfgName, line_num); + continue; + } + 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; +}*/ + +/** + * Reading main configuration file. + * @param cfgName: Name of the configuration (could be fullpath) + * @param normal: Config read normally when server started + * @return True:success, Fals:failure (file not found|readable) + */ +bool brokk_config_read(const char* cfgName, bool normal) { + char line[1024], w1[32], w2[1024]; + FILE* fp = fopen(cfgName, "r"); + if (fp == nullptr) { + ShowError("Configuration file (%s) not found.\n", cfgName); + return false; + } + while (fgets(line, sizeof(line), fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%31[^:]: %1023[^\r\n]", w1, w2) < 2) + continue; + + // Config that loaded only when server started, not by reloading config file + if (normal) { + if (!strcmpi(w1, "bind_ip")) { + brokk_config.brokk_ip = host2ip(w2); + if (brokk_config.brokk_ip) { + char ip_str[16]; + ShowStatus("Brokk server binding IP address : %s -> %s\n", w2, ip2str(brokk_config.brokk_ip, ip_str)); + } + } + else if (!strcmpi(w1, "brokk_port")) + brokk_config.brokk_port = (uint16)atoi(w2); + else if (!strcmpi(w1, "console")) + brokk_config.console = (bool)config_switch(w2); + } + + if (!strcmpi(w1, "timestamp_format")) + safestrncpy(timestamp_format, w2, 20); + else if (strcmpi(w1, "db_path") == 0) + safestrncpy(db_path, w2, ARRAYLENGTH(db_path)); + 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, "console_msg_log") == 0) + console_msg_log = atoi(w2); + else if (strcmpi(w1, "console_log_filepath") == 0) + safestrncpy(console_log_filepath, w2, sizeof(console_log_filepath)); + else if (!strcmpi(w1, "log_brokk")) + brokk_config.log_brokk = (bool)config_switch(w2); + else if (!strcmpi(w1, "date_format")) + safestrncpy(brokk_config.date_format, w2, sizeof(brokk_config.date_format)); + else if (!strcmpi(w1, "allowed_bot_creations")) + brokk_config.allowed_bot_creations = (uint32)atoi(w2); + else if (!strcmpi(w1, "destroy_all_on_shutdown")) + brokk_config.destroy_all_on_shutdown = atoi(w2); + else if (!strcmpi(w1, "acc_name_min_length")) + brokk_config.acc_name_min_length = cap_value(atoi(w2), 0, NAME_LENGTH - 1); + else if (!strcmpi(w1, "password_min_length")) + brokk_config.password_min_length = cap_value(atoi(w2), 0, PASSWD_LENGTH - 1); + else if (!strcmpi(w1, "time_allowed")) + brokk_config.time_allowed = atoi(w2); + else if (!strcmpi(w1, "import")) + brokk_config_read(w2, normal); + else {// try the account engines + if (!normal) + continue; + if (login_accounts && login_accounts->set_property(login_accounts, w1, w2)) + continue; + } + } + fclose(fp); + ShowInfo("Finished reading %s.\n", cfgName); + return true; +} + +/** + * Init brokk-serv default configuration. + */ +void brokk_set_defaults() { + brokk_config.brokk_ip = INADDR_ANY; + brokk_config.brokk_port = 7500; + brokk_config.log_brokk = true; + safestrncpy(brokk_config.date_format, "%Y-%m-%d %H:%M:%S", sizeof(brokk_config.date_format)); + brokk_config.console = false; + brokk_config.destroy_all_on_shutdown = 0; //Default all Bots are preserved on shutdown + brokk_config.allowed_bot_creations = 10; + brokk_config.acc_name_min_length = 6; + brokk_config.password_min_length = 6; + brokk_config.time_allowed = 10; //in second + + //other default conf + safestrncpy(brokk_config.brokkconf_name, "conf/brokk_athena.conf", sizeof(brokk_config.brokkconf_name)); + safestrncpy(brokk_config.msgconf_name, "conf/msg_conf/brokk_msg.conf", sizeof(brokk_config.msgconf_name)); +} + + +void bot_account_cleanup() { + struct mmo_bot_account bot_acc; + BotAccountDBIterator* iter_bots = bot_accounts->iterator(bot_accounts); + std::vector account_ids_to_delete; + + // First pass: Collect account_ids to delete based on config + while (iter_bots->next(iter_bots, &bot_acc)) { + // Check based on config + if (brokk_config.destroy_all_on_shutdown == 2 || (brokk_config.destroy_all_on_shutdown == 1 && strcmp(bot_acc.creator_name, "Brokk") == 0)) { + // Collect account_id for deletion + account_ids_to_delete.push_back(bot_acc.account_id); + } + } + + // Destroy the iterator after use + iter_bots->destroy(iter_bots); + + // Second pass: Delete the collected accounts + for (const auto& account_id : account_ids_to_delete) { + ShowInfo("Deleting bot account with ID %d.\n", account_id); + brokk_mmo_auth_delete_num(account_id); // Delete from `login`, `bot_accounts` and 'char' tables + } + + ShowNotice("Bot account destruction completed successfully.\n\n"); +} +/* +* initializes dbs for all used non-yaml databases +* returns true if successfull +*/ +bool brokk_initialize_dbs() +{ + // Account database init (login table) + if (login_accounts == nullptr) { + ShowFatalError("do_init: account engine not found.\n"); + return false; + } + else { + if (!login_accounts->init(login_accounts)) { + ShowFatalError("do_init: Failed to initialize account engine.\n"); + return false; + } + } + // bot Account database init (bot_accounts table) + if (bot_accounts == nullptr) { + ShowFatalError("do_init: bot account engine not found.\n"); + return false; + } + else { + if (!bot_accounts->init(bot_accounts)) { + ShowFatalError("do_init: Failed to initialize bot account engine.\n"); + return false; + } + } + + // Char database init (char table) + if (charakters == nullptr) { + ShowFatalError("do_init: charakter engine not found.\n"); + return false; + } + else { + if (!charakters->init(charakters)) { + ShowFatalError("do_init: Failed to initialize charakters engine.\n"); + return false; + } + } + + // Skill database init (skill table) + if (skills == nullptr) { + ShowFatalError("do_init: skill engine not found.\n"); + return false; + } + else { + if (!skills->init(skills)) { + ShowFatalError("do_init: Failed to initialize skill engine.\n"); + return false; + } + } + + return true; +} + +// Convert time_t to string and store it in last_login (char[24]) +void set_last_login(char last_login[24]) { + time_t now = time(nullptr); + struct tm timeinfo; + +#if defined(_WIN32) || defined(_WIN64) + // Windows-specific localtime_s version + localtime_s(&timeinfo, &now); +#else + // Standard version for POSIX systems + localtime_r(&now, &timeinfo); +#endif + + // Format time as string "YYYY-MM-DD HH:MM:SS" + strftime(last_login, 24, "%Y-%m-%d %H:%M:%S", &timeinfo); +} + +/** + * Create a new charakter and save it in db/sql. + * @param account_id: int for account, the charakter belongs to + * @param char_name: string for charakter name + * @param slot: int which charakter slot to put it in (char_num in db) + * @param : stats of the charakter (str,agi,vit,etc) + * @param hair_style: int for hair_style id + * @param hair_color: int for hair_color id * + * @param start_job: short for job class + * @param sex: string for gender of character + * @return : + * -1: success + * 0: unregistered id (invalid sex or failure to create in db) + * 1: incorrect char_name (char_name too short or already exists) + * 2: Error while writing into char table + * 3: Registration denied (limit reached) + */ +int brokk_create_character(uint32 account_id, const char* char_name, int slot, int base_level, int job_level, int str, int agi, int vit, int int_, int dex, int luk, int hair_style, int hair_color, short start_job, char sex, const bool auto_generated) { + static int num_regs = 0; // char registration counter + static t_tick new_reg_tick = 0; + t_tick tick = gettick(); + struct mmo_charakter charakter; + struct mmo_account acc; + + // Check if the account exists + if (!login_accounts->load_num(login_accounts, &acc, account_id)) { + ShowNotice("Attempt to create char for non-existent account (account_id: %s)\n", account_id); + return 0; // Account does not exist + } + // char registration flood protection + if (new_reg_tick == 0) + new_reg_tick = gettick(); + if (auto_generated == false) { + if (DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= brokk_config.allowed_bot_creations) { + ShowNotice("Charakter registration denied (registration limit exceeded)\n"); + return 3; + } + } + + // Ensure valid sex input (M/F for bots) + if (sex != 'M' && sex != 'F') + return 0; // Unregistered ID + + // Check if the char already exists + if (charakters->load_str(charakters, &charakter, char_name)) { + ShowNotice("Attempt to create an already existing charakter (CharName: %s, sex: %c)\n", char_name, sex); + return 1; // Char exists + } + + //TODO Herausfinden welche char Level und stats wann plausibel sind. + // -> Plausibility checks aus map.cpp extrahieren + //TODO2 Homogene Base-Level grenzen herausfinden (bei welchem Base-Level ist man welches Job-Level bei Renewal + + // Create charakter + // Clear and populate charakter structure + memset(&charakter, '\0', sizeof(charakter)); + charakter.char_id = -1; // Assigned by char db + // Assign values from parameters + charakter.account_id = account_id; + charakter.char_num = slot; + safestrncpy(charakter.name, char_name, sizeof(charakter.name)); + charakter.str = str; + charakter.agi = agi; + charakter.vit = vit; + charakter.int_ = int_; + charakter.dex = dex; + charakter.luk = luk; + charakter.hair = hair_style; + charakter.hair_color = hair_color; + charakter.class_ = start_job; + charakter.sex = sex; + + // Default values for new character creation + charakter.base_level = base_level; + charakter.job_level = job_level; + charakter.base_exp = bot_job_db.get_baseExp(start_job, base_level); + charakter.job_exp = bot_job_db.get_jobExp(start_job, job_level); + charakter.zeny = 1000000; + charakter.pow = 0; + charakter.sta = 0; + charakter.wis = 0; + charakter.spl = 0; + charakter.con = 0; + charakter.crt = 0; + + charakter.max_hp = 40 * (100 + vit) / 100; // Default calculation for max HP + charakter.hp = charakter.max_hp; + charakter.max_sp = 11 * (100 + int_) / 100; // Default calculation for max SP + charakter.sp = charakter.max_sp; + charakter.max_ap = 0; // Default AP for new characters + charakter.ap = 0; + + // Calculate Status Points from current level + charakter.status_point = bot_statpoint_db.get_table_point(base_level); + charakter.skill_point = 0; + charakter.trait_point = bot_statpoint_db.get_trait_table_point(base_level);; + charakter.option = 0; + charakter.karma = 0; + charakter.manner = 0; + + charakter.party_id = 0; + charakter.guild_id = 0; + charakter.pet_id = 0; + charakter.homun_id = 0; + charakter.elemental_id = 0; + charakter.body = 0; + charakter.weapon = 0; + charakter.shield = 0; + charakter.head_top = 0; + charakter.head_mid = 0; + charakter.head_bottom = 0; + charakter.robe = 0; + + safestrncpy(charakter.last_map, "morocc", sizeof(charakter.last_map)); // Default spawn map + charakter.last_x = 159; // Default spawn position x + charakter.last_y = 91; // Default spawn position y + charakter.last_instanceid = 0; + + safestrncpy(charakter.save_map, "morocc", sizeof(charakter.save_map)); // Default save point map + charakter.save_x = 156; // Default save point position x + charakter.save_y = 45; // Default save point position y + + /*TODO Meta Profiles hier laden : Für payon_citizen + * safestrncpy(charakter.save_map, "payon", sizeof(charakter.save_map)); // Default save point map + * charakter.save_x = 161; // Default save point position x + * charakter.save_y = 57; // Default save point position y + */ + + charakter.partner_id = 0; + charakter.online = 0; + charakter.fame = 0; + charakter.rename = 0; + charakter.delete_date = 0; + charakter.moves = 0; + charakter.unban_time = 0; + charakter.font = 0; + charakter.uniqueitem_counter = 0; + charakter.hotkey_rowshift = 0; + charakter.hotkey_rowshift2 = 0; + charakter.clan_id = 0; + charakter.title_id = 0; + charakter.show_equip = 0; + charakter.inventory_slots = 100; // Default inventory slots + charakter.body_direction = 0; + charakter.disable_call = 0; + + // Set default timestamp for last login to current time (or keep it empty for new character) + memset(charakter.last_login, '\0', sizeof(charakter.last_login)); // Clear the memory + set_last_login(charakter.last_login); + +//#ifdef VIP_ENABLE + +//#endif + // Create the account in the database + if (!charakters->create(charakters, &charakter)) + return 0; // Charakter creation failed + + ShowNotice("Bot charakter creation successful (name: %s, char_id: %d, sex: %c)\n", charakter.name, charakter.char_id, charakter.sex); + + + // Registration rate limit logic + if (DIFF_TICK(tick, new_reg_tick) > 0) { + num_regs = 0; + new_reg_tick = tick + brokk_config.time_allowed * 1000; + } + + ++num_regs; + //ShowInfo("Bot character created: %s (account: %s, char_name: %d)\n", charakter.account_id, charakter.name); + return -1; +} + +void BrokkServer::finalize() { + AccountDB* db = login_accounts; + BotAccountDB* bot_db = bot_accounts; + CharDB* char_db = charakters; + SkillDB* skill_db = skills; + + //brokk_log(0, "brokk server", 100, "brokk server shutdown"); + ShowStatus("Terminating...\n"); + //if (brokk_config.log_brokk) + // brokklog_final(); + + // Attempt to delete the bot account on shutdown + bot_account_cleanup(); + + if (db) { // destroy account engine + db->destroy(db); + db = nullptr; + } + + if (bot_db) { // destroy bot account engine + bot_db->destroy(bot_db); + bot_db = nullptr; + } + + if (char_db) { // destroy charakter engine + char_db->destroy(char_db); + char_db = nullptr; + } + + if (skill_db) { // destroy skill engine + skill_db->destroy(skill_db); + skill_db = nullptr; + } + + login_accounts = nullptr; // destroyed in account_engine + bot_accounts = nullptr; // destroyed in bot account_engine + charakters = nullptr;// destroyed in charakter engine + skills = nullptr; //destroy in skills engine + + if (brokk_fd != -1) + { + do_close(brokk_fd); + brokk_fd = -1; + } + ShowStatus("Finished.\n"); } @@ -37,7 +773,226 @@ void BrokkServer::handle_shutdown() { } bool BrokkServer::initialize(int argc, char* argv[]) { + // Init default value + safestrncpy(console_log_filepath, "./log/brokk-msg_log.log", sizeof(console_log_filepath)); + + // initialize engine + login_accounts = account_db_sql(); + struct mmo_account acc; + bot_accounts = bot_account_db_sql(); + charakters = char_db_sql(); + skills = skill_db_sql(); + + // Initialize Yaml-DBs + bot_read_yaml_dbs(); + + // read brokk-server configuration + brokk_set_defaults(); + + brokk_config_read(brokk_config.brokkconf_name, true); + + if (!brokk_initialize_dbs()) { + //Message will be triggered inside init + return false; + } + + /**/ + //debug skills von gm acc aufzählen + SkillDBIterator* iter_skills = skills->iterator(skills, 150000); + struct bot_skill skill; + while (iter_skills->next(iter_skills, 150000, &skill)) { + // Check based on config + ShowDebug("Skill %s (LV: %d) found in gm acc.\n", convert_skill_id_to_str(skill.skill_id), skill.level); + } + // Destroy the iterator after use + iter_skills->destroy(iter_skills); + + // initialize logging + //if (brokk_config.log_brokk) + // brokklog_init(); + + // server port open & binding + if ((brokk_fd = make_listen_bind(brokk_config.brokk_ip, brokk_config.brokk_port)) == -1) { + ShowFatalError("Failed to bind to port '" CL_WHITE "%d" CL_RESET "'\n", brokk_config.brokk_port); + return false; + } + + // Trigger bot account creation by reading config for bot creation (conf_bot_acc.json) + // Open the JSON file + std::ifstream bot_accounts_file("conf/conf_bot_acc.json"); + if (!bot_accounts_file.is_open()) { + ShowError("Failed to open conf_bot_acc.json\n"); + return false; + } + + // Parse the JSON file + json json_bot_accounts; + try { + bot_accounts_file >> json_bot_accounts; + + } + catch (json::parse_error& e) { + ShowError("Error parsing bot_accounts.json: %s\n", e.what()); + return false; + } + // Close file after done with it + bot_accounts_file.close(); + + ShowStatus("Starting to create bot accounts...\n"); + + // Iterate over each bot entry in the JSON file + for (const auto& bot : json_bot_accounts) { + const std::string userid_str = bot["userid"]; + const char* userid = userid_str.c_str(); + const std::string pass = bot["pass"]; + const char sex = bot["sex"].get()[0]; // Extract single char from string + const std::string last_ip = bot["last_ip"]; + + // Debug: Print the entire bot entry + //ShowInfo("Processing bot: %s\n", bot.dump(4).c_str()); + + // Debug: Iterate over the keys in the bot object and print them + //for (auto it = bot.begin(); it != bot.end(); ++it) { + // ShowInfo("Key: %s\n", it.key().c_str()); + //} + // DEBUG: Show bot details + //ShowStatus("Processing bot: userid=%s, pass=%s, sex=%c\n", userid, pass.c_str(), sex); + + // Call brokk_mmo_auth_new to handle account creation + if (!brokk_mmo_auth_new(userid, pass.c_str(), sex, last_ip.c_str(), true)) { + ShowError("Failed to create bot account (userid: %s)\n", userid); + continue; // Skip to the next bot account + } + + // Iterate over each character for this bot account + // Retrieve the account ID for the newly created account (assuming your function updates a global or accessible database) + // Check if the bot account already exists + if (!login_accounts->load_str(login_accounts, &acc, userid)) { + ShowNotice("Error in loading Account_id. Please check the following account data (account: %s, sex: %c)\n", userid, sex); + return 1; // Account exists + } + uint32 account_id = acc.account_id; + // Iterate over each character for this bot account + if (bot.contains("characters")) { + const auto& characters = bot["characters"]; + + //TODO ähnlichen Test für skills einbauen + for (const auto& character : characters) { + const std::string char_name = character["name"]; + const int slot = character["slot"]; + + const int base_level = character["base_level"]; + const int job_level = character["job_level"]; + + const int hair_style = character["hair_style"]; + const int hair_color = character["hair_color"]; + //TODO: Clothing/Style Randomizer hier einbauen + + //Decode Job Value (is given as the ID not the number behind the ID for readability) + const std::string job_id = character["start_job"]; + const int start_job = convert_jobid_to_number(job_id.c_str()); + if (start_job == -1) { + const int start_job = JOB_NOVICE; + } + + std::shared_ptr job = bot_job_db.find(start_job); + + if (base_level > bot_job_db.get_maxBaseLv(start_job) or job_level > bot_job_db.get_maxJobLv(start_job)) + { + //ShowNotice("Error in bot data. Inplausible baselevel (max=%s) or joblevel (max=%s) (account: %s, charname: $s, baselevel: %d, joblevel: %d)\n", bot_job_db.get_maxBaseLv(start_job), bot_job_db.get_maxJobLv(start_job), userid, char_name, base_level, job_level); + ShowNotice("Unplausible Base-level/Job-level"); + continue; // Inplausible data + } + + // Use job_id as reference for stat_builds + const std::string build_profile = character["char_build_profile"]; + // Prep with default + int str = 1; + int agi = 1; + int vit = 1; + int int_ = 1; + int dex = 1; + int luk = 1; + if (build_profile == "CUSTOM") { + // When a CUSTOM build is entered, stats key must be present + if (!character.contains("stats")) { + // character params in json are malformed. Stats for CUSTOM build are missing + ShowNotice("Error: Could not create character %s . Stats are missing.", char_name); + continue; + } + + const auto& char_stats = character["stats"]; + str = char_stats["str"]; + agi = char_stats["agi"]; + vit = char_stats["vit"]; + int_ = char_stats["int"]; + dex = char_stats["dex"]; + luk = char_stats["luk"]; + } + else { + // Open the JSON file + std::string job_profile_file_path = "conf/bot_profiles/"; + job_profile_file_path += job_id.c_str(); + job_profile_file_path += "_profiles.json"; + std::ifstream stat_profile_file(job_profile_file_path); + if (!stat_profile_file.is_open()) { + ShowError("Failed to open %s\n", job_profile_file_path.c_str()); + return false; + } + + // Parse the JSON file + json json_stat_profiles; + try { + stat_profile_file >> json_stat_profiles; + + } + catch (json::parse_error& e) { + ShowError("Error parsing %s for job: %s\n", job_profile_file_path.c_str(), job_id.c_str()); + return false; + } + // Close file after done with it + stat_profile_file.close(); + + // Read profile data and convert to stats + if (!json_stat_profiles.contains(build_profile)) { + ShowError("Error build profile %s does not exists. Skipping char...\n", build_profile.c_str()); + return false; + } + + const auto& stats = json_stat_profiles[build_profile]; + str = stats["str"]; + agi = stats["agi"]; + vit = stats["vit"]; + int_ = stats["int"]; + dex = stats["dex"]; + luk = stats["luk"]; + } + + // DEBUG: Show character details + //ShowStatus("Creating character: name=%s, slot=%d, start_job=%d\n", char_name.c_str(), slot, start_job); + + // Create the character for this account + if (!brokk_create_character(account_id, char_name.c_str(), slot, base_level, job_level, str, agi, vit, int_, dex, luk, hair_style, hair_color, start_job, sex, true)) + { + ShowError("Failed to create charakter (char_name: %s)\n", char_name); + continue; // Skip to the next charakter + } + } + + } + else { + ShowError("No 'characters' array found for bot (userid: %s)\n", userid); + } + + } + + // Other initialization logic + setup_periodic_cleanup_timer(); // Set up the cleanup timer + + ShowStatus("The brokk-server is " CL_GREEN "ready" CL_RESET " (Server is listening on the port %u).\n\n", brokk_config.brokk_port); + //brokk_log(0, "brokk server", 100, "brokk server started"); + return true; } diff --git a/src/bot/Brokk/brokk.hpp b/src/bot/Brokk/brokk.hpp index 2651428..bee7fd7 100644 --- a/src/bot/Brokk/brokk.hpp +++ b/src/bot/Brokk/brokk.hpp @@ -34,14 +34,20 @@ namespace rathena { struct Brokk_Config { uint32 brokk_ip; /// the address to bind to uint16 brokk_port; /// the port to bind to - bool log_login; /// whether to log login server actions or not + bool log_brokk; /// whether to log brokk server actions or not char date_format[32]; /// date format used in messages bool console; /// console input system enabled? - char loginconf_name[256]; /// name of main config file + char brokkconf_name[256]; /// name of main config file char msgconf_name[256]; /// name of msg_conf config file - char lanconf_name[256]; /// name of lan config file + bool destroy_all_on_shutdown; /// cleanup all bots on shutdown? + uint32 allowed_bot_creations; /// number of allowed bot creations in quick succession + uint8 acc_name_min_length; /// minimum account name length + uint8 password_min_length; /// minimum password length + int time_allowed; /// registration interval in seconds + bool use_web_auth_token; /// Enable web authentication token system + int disable_webtoken_delay; /// delay disabling web token after char logs off in milliseconds #ifdef VIP_ENABLE @@ -49,4 +55,16 @@ struct Brokk_Config { }; extern struct Brokk_Config brokk_config; + +// Bot Account creation +int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip, const bool auto_generated, int creator_id, const char* creator_name); +int brokk_mmo_auth_delete_str(const char* userid); +int brokk_mmo_auth_delete_num(uint32 account_id); +bool brokk_config_read(const char* cfgName, bool normal); +void brokk_set_defaults(); +void setup_periodic_cleanup_timer(); +void bot_account_cleanup(); +bool brokk_initialize_dbs(); + + #endif /* BROKK_HPP */ diff --git a/src/bot/Brokk/char_creation_helpers.cpp b/src/bot/Brokk/char_creation_helpers.cpp new file mode 100644 index 0000000..7e24202 --- /dev/null +++ b/src/bot/Brokk/char_creation_helpers.cpp @@ -0,0 +1,1426 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#include "char_creation_helpers.hpp" + +#include +#include + +#include +#include // safestrncpy() +#include +#include + +#include "charakters.hpp" + + +using namespace rathena; + +/* +* Statpoint Functions & Constants +*/ +#ifdef RENEWAL_STAT +/// Renewal status point cost formula +#define PC_STATUS_POINT_COST(low) (((low) < 100) ? (2 + ((low) - 1) / 10) : (16 + 4 * (((low) - 100) / 5))) +#else +/// Pre-Renewal status point cost formula +#define PC_STATUS_POINT_COST(low) (( 1 + ((low) + 9) / 10 )) +#endif + +/** + * Gets the total number of status points at the provided level. + * @param level: Player base level. + * @return Total number of status points at specific base level. + */ +uint32 BotStatPointDatabase::get_table_point(uint16 level) { + std::shared_ptr entry = this->find(level); + + if (entry != nullptr) { + return entry->statpoints; + } + else { + return 0; + } +} + + +/** +*Calculates the number of status points PC gets when leveling up(from level to level + 1) +* @param level : Player base level. +* @param table : Use table value or formula. +* @return Status points at specific base level. +*/ +uint32 BotStatPointDatabase::load_max_status_points_of_level(uint16 level) { + uint32 next_level = this->get_table_point(level + 1); + uint32 current_level = this->get_table_point(level); + + if (next_level > current_level) { + return next_level - current_level; + } + else { + return 0; + } +} + +/** +* Gets the total number of trait points at the provided level. +* @param level: Player base level. +* @return Total number of trait points at specific base level. +*/ +uint32 BotStatPointDatabase::get_trait_table_point(uint16 level) { + std::shared_ptr entry = this->find(level); + + if (entry != nullptr) { + return entry->traitpoints; + } + else { + return 0; + } +} + +/** +* Calculates the number of trait points PC gets when leveling up (from level to level+1) +* @param level: Player base level. +* @param table: Use table value or formula. +* @return Trait points at specific base level. +*/ +uint32 BotStatPointDatabase::load_max_trait_points_of_level(uint16 level) { + uint32 next_level = this->get_trait_table_point(level + 1); + uint32 current_level = this->get_trait_table_point(level); + + if (next_level > current_level) { + return next_level - current_level; + } + else { + return 0; + } +} + +const std::string BotStatPointDatabase::getDefaultLocation() { + return std::string(db_path) + "/statpoint.yml"; +} + +uint64 BotStatPointDatabase::parseBodyNode(const ryml::NodeRef& node) { + + uint16 level; + + if (!this->asUInt16(node, "Level", level)) + return 0; + + if (level == 0) { + this->invalidWarning(node["Level"], "The minimum level is 1.\n"); + return 0; + } + /* + if (level > MAX_LEVEL) { + this->invalidWarning(node["Level"], "Level %d exceeds maximum BaseLevel %d, skipping.\n", level, MAX_LEVEL); + return 0; + }*/ + + std::shared_ptr entry = this->find(level); + bool exists = entry != nullptr; + + if (!exists) { + if (!this->nodesExist(node, { "Points" })) { + return 0; + } + + entry = std::make_shared(); + entry->level = level; + } + + if (this->nodeExists(node, "Points")) { + uint32 points; + + if (!this->asUInt32(node, "Points", points)) { + return 0; + } + + entry->statpoints = points; + } + + if (this->nodeExists(node, "TraitPoints")) { + uint32 traitpoints; + + if (!this->asUInt32(node, "TraitPoints", traitpoints)) { + return 0; + } + + entry->traitpoints = traitpoints; + } + else { + if (!exists) { + entry->traitpoints = 0; + } + } + + if (!exists) { + this->put(level, entry); + } + + return 1; +} + +/** + * Generate the remaining parts of the db if necessary. + */ +void BotStatPointDatabase::loadingFinished() { + const uint16 trait_start_level = 200; + std::shared_ptr level_one = this->find(1); + + if (level_one == nullptr) { + + level_one = std::make_shared(); + + level_one->level = 1; + level_one->statpoints = 48; + level_one->traitpoints = 0; + + this->put(1, level_one); + } + else { + level_one->statpoints = 48; + level_one->traitpoints = 0; + } + + std::shared_ptr last_level = level_one; + + for (uint16 level = 2; level <= 275; level++) { //MAX_LEVEL + std::shared_ptr entry = this->find(level); + bool exists = entry != nullptr; + + if (!exists) { + entry = std::make_shared(); + entry->level = level; + this->put(level, entry); + } + + if (!exists) { + if (level <= trait_start_level) { + entry->statpoints = last_level->statpoints + ((level - 1 + 15) / 5); + } + else { + entry->statpoints = last_level->statpoints; + } + } + + if (!exists) { + + if (level > trait_start_level) { + entry->traitpoints = (level - trait_start_level) * 3 + (level - trait_start_level) / 5 * 4; + } + else { + entry->traitpoints = 0; + } + } + + // Store it for next iteration + last_level = entry; + } + + TypesafeCachedYamlDatabase::loadingFinished(); +} + +/* +* Job Data Functions +*/ + +/*==================================================== + * 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(nullptr, 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(nullptr, 557 - JOB_KNIGHT + class_); + + case JOB_KNIGHT2: + return msg_txt(nullptr, 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(nullptr, 563 - JOB_CRUSADER + class_); + + case JOB_CRUSADER2: + return msg_txt(nullptr, 563); + + case JOB_WEDDING: + case JOB_SUPER_NOVICE: + case JOB_GUNSLINGER: + case JOB_NINJA: + case JOB_XMAS: + return msg_txt(nullptr, 570 - JOB_WEDDING + class_); + + case JOB_SUMMER: + case JOB_SUMMER2: + return msg_txt(nullptr, 621); + + case JOB_HANBOK: + return msg_txt(nullptr, 694); + + case JOB_OKTOBERFEST: + return msg_txt(nullptr, 696); + + 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(nullptr, 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(nullptr, 582 - JOB_LORD_KNIGHT + class_); + + case JOB_LORD_KNIGHT2: + return msg_txt(nullptr, 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(nullptr, 588 - JOB_PALADIN + class_); + + case JOB_PALADIN2: + return msg_txt(nullptr, 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(nullptr, 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(nullptr, 602 - JOB_BABY_KNIGHT + class_); + + case JOB_BABY_KNIGHT2: + return msg_txt(nullptr, 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(nullptr, 608 - JOB_BABY_CRUSADER + class_); + + case JOB_BABY_CRUSADER2: + return msg_txt(nullptr, 608); + + case JOB_SUPER_BABY: + return msg_txt(nullptr, 615); + + case JOB_TAEKWON: + return msg_txt(nullptr, 616); + case JOB_STAR_GLADIATOR: + case JOB_STAR_GLADIATOR2: + return msg_txt(nullptr, 617); + case JOB_SOUL_LINKER: + return msg_txt(nullptr, 618); + + case JOB_GANGSI: + case JOB_DEATH_KNIGHT: + case JOB_DARK_COLLECTOR: + return msg_txt(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 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(nullptr, 687 - JOB_ROYAL_GUARD_T + class_); + + case JOB_RUNE_KNIGHT2: + case JOB_RUNE_KNIGHT_T2: + return msg_txt(nullptr, 625); + + case JOB_ROYAL_GUARD2: + case JOB_ROYAL_GUARD_T2: + return msg_txt(nullptr, 631); + + case JOB_RANGER2: + case JOB_RANGER_T2: + return msg_txt(nullptr, 627); + + case JOB_MECHANIC2: + case JOB_MECHANIC_T2: + return msg_txt(nullptr, 629); + + case JOB_BABY_RUNE_KNIGHT: + case JOB_BABY_WARLOCK: + case JOB_BABY_RANGER: + case JOB_BABY_ARCH_BISHOP: + case JOB_BABY_MECHANIC: + case JOB_BABY_GUILLOTINE_CROSS: + case JOB_BABY_ROYAL_GUARD: + case JOB_BABY_SORCERER: + case JOB_BABY_MINSTREL: + case JOB_BABY_WANDERER: + case JOB_BABY_SURA: + case JOB_BABY_GENETIC: + case JOB_BABY_SHADOW_CHASER: + return msg_txt(nullptr, 638 - JOB_BABY_RUNE_KNIGHT + class_); + + case JOB_BABY_RUNE_KNIGHT2: + return msg_txt(nullptr, 638); + + case JOB_BABY_ROYAL_GUARD2: + return msg_txt(nullptr, 644); + + case JOB_BABY_RANGER2: + return msg_txt(nullptr, 640); + + case JOB_BABY_MECHANIC2: + return msg_txt(nullptr, 642); + + case JOB_SUPER_NOVICE_E: + case JOB_SUPER_BABY_E: + return msg_txt(nullptr, 651 - JOB_SUPER_NOVICE_E + class_); + + case JOB_KAGEROU: + case JOB_OBORO: + return msg_txt(nullptr, 653 - JOB_KAGEROU + class_); + + case JOB_REBELLION: + return msg_txt(nullptr, 695); + + case JOB_SUMMONER: + return msg_txt(nullptr, 697); + case JOB_BABY_SUMMONER: + return msg_txt(nullptr, 698); + case JOB_BABY_NINJA: + return msg_txt(nullptr, 699); + + case JOB_BABY_KAGEROU: + case JOB_BABY_OBORO: + case JOB_BABY_TAEKWON: + case JOB_BABY_STAR_GLADIATOR: + case JOB_BABY_SOUL_LINKER: + case JOB_BABY_GUNSLINGER: + case JOB_BABY_REBELLION: + return msg_txt(nullptr, 753 - JOB_BABY_KAGEROU + class_); + + case JOB_BABY_STAR_GLADIATOR2: + return msg_txt(nullptr, 756); + + case JOB_STAR_EMPEROR: + case JOB_SOUL_REAPER: + case JOB_BABY_STAR_EMPEROR: + case JOB_BABY_SOUL_REAPER: + return msg_txt(nullptr, 782 - JOB_STAR_EMPEROR + class_); + + case JOB_STAR_EMPEROR2: + return msg_txt(nullptr, 782); + + case JOB_BABY_STAR_EMPEROR2: + return msg_txt(nullptr, 784); + + case JOB_DRAGON_KNIGHT: + case JOB_MEISTER: + case JOB_SHADOW_CROSS: + case JOB_ARCH_MAGE: + case JOB_CARDINAL: + case JOB_WINDHAWK: + case JOB_IMPERIAL_GUARD: + case JOB_BIOLO: + case JOB_ABYSS_CHASER: + case JOB_ELEMENTAL_MASTER: + case JOB_INQUISITOR: + case JOB_TROUBADOUR: + case JOB_TROUVERE: + return msg_txt(nullptr, 800 - JOB_DRAGON_KNIGHT + class_); + + case JOB_WINDHAWK2: + return msg_txt(nullptr, 805); + + case JOB_MEISTER2: + return msg_txt(nullptr, 801); + + case JOB_DRAGON_KNIGHT2: + return msg_txt(nullptr, 800); + + case JOB_IMPERIAL_GUARD2: + return msg_txt(nullptr, 806); + + case JOB_SKY_EMPEROR: + case JOB_SOUL_ASCETIC: + case JOB_SHINKIRO: + case JOB_SHIRANUI: + case JOB_NIGHT_WATCH: + case JOB_HYPER_NOVICE: + case JOB_SPIRIT_HANDLER: + return msg_txt(nullptr, 813 - JOB_SKY_EMPEROR + class_); + + case JOB_SKY_EMPEROR2: + return msg_txt(nullptr, 813); + + default: + return msg_txt(nullptr, 655); + } +}*/ + +/** + * Returns max base level for this character's class. + * @param job_id: Player's class + * @return Max Base Level + */ +uint32 BotJobDatabase::get_maxBaseLv(uint16 job_id) { + std::shared_ptr job = bot_job_db.find(job_id); + + return job ? job->max_base_level : 0; +} + +/** + * Returns max base level for this character. + * @param sd Player + * @return Max Base Level + **/ +unsigned int bot_maxbaselv(short class_) { + return bot_job_db.get_maxBaseLv(class_); +} + +/** + * Returns max job level for this character's class. + * @param job_id: Player's class + * @return Max Job Level + */ +uint32 BotJobDatabase::get_maxJobLv(uint16 job_id) { + std::shared_ptr job = bot_job_db.find(job_id); + + return job ? job->max_job_level : 0; +} + +/** + * Returns max job level for this character. + * @param class_ + * @return Max Job Level + **/ +unsigned int bot_maxjoblv(short class_) { + return bot_job_db.get_maxJobLv(class_); +} + +/** + * Check if player is reached max base level + * @param class_ + * @param base_level + * @return True if reached max level + **/ +bool bot_is_maxbaselv(short class_, unsigned int base_level) { + return (base_level >= bot_maxbaselv(class_)); +} + +/** + * Check if player is reached max base level + * @param class_ + * @param job_level + * @return True if reached max level + **/ +bool bot_is_maxjoblv(short class_, unsigned int job_level) { + return (job_level >= bot_maxjoblv(class_)); +} + + +/** + * Returns max weight base for this character's class. + * @param job_id: Player's class + * @return Max weight base + */ +int32 BotJobDatabase::get_maxWeight(uint16 job_id) { + std::shared_ptr job = bot_job_db.find(job_id); + + return job ? job->max_weight_base : 0; +} + +/** + * Calculates base hp of player. Reference: http://irowiki.org/wiki/Max_HP + * @param level: Base level of player + * @param job_id: Job ID @see enum e_job + * @return base_hp + * @author [Cydh] + */ +static unsigned int pc_calc_basehp(uint16 level, uint16 job_id) { + std::shared_ptr job = bot_job_db.find(job_id); + double base_hp = 35 + level * (job->hp_increase / 100.); + +#ifndef RENEWAL + if (level >= 10 && (job_id == JOB_NINJA || job_id == JOB_GUNSLINGER)) + base_hp += 90; +#endif + for (uint16 i = 2; i <= level; i++) + base_hp += floor(((job->hp_factor / 100.) * i) + 0.5); //Don't have round() + if (job_id == JOB_SUMMONER || job_id == JOB_SPIRIT_HANDLER) + base_hp += floor((base_hp / 2) + 0.5); + return (unsigned int)base_hp; +} + +/** + * Calculates base sp of player. + * @param level: Base level of player + * @param job_id: Job ID @see enum e_job + * @return base_sp + * @author [Playtester] + */ +static unsigned int pc_calc_basesp(uint16 level, uint16 job_id) { + std::shared_ptr job = bot_job_db.find(job_id); + double base_sp = 10 + floor(level * (job->sp_increase / 100.)); + + switch (job_id) { + case JOB_NINJA: + if (level >= 10) + base_sp -= 22; + else + base_sp = 11 + 3 * level; + break; + case JOB_GUNSLINGER: + if (level > 10) + base_sp -= 18; + else + base_sp = 9 + 3 * level; + break; + case JOB_SUMMONER: + case JOB_SPIRIT_HANDLER: + base_sp -= floor(base_sp / 2); + break; + } + + return (unsigned int)base_sp; +} + +/*========================================== + * Convert's from the client's lame Job ID system + * to the map server's 'makes sense' system. [Skotlex] + *------------------------------------------*/ +uint64 pc_jobid2mapid(unsigned short b_class) +{ + switch (b_class) + { + //Novice And 1-1 Jobs + case JOB_NOVICE: return BOT_MAPID_NOVICE; + case JOB_SWORDMAN: return BOT_MAPID_SWORDMAN; + case JOB_MAGE: return BOT_MAPID_MAGE; + case JOB_ARCHER: return BOT_MAPID_ARCHER; + case JOB_ACOLYTE: return BOT_MAPID_ACOLYTE; + case JOB_MERCHANT: return BOT_MAPID_MERCHANT; + case JOB_THIEF: return BOT_MAPID_THIEF; + case JOB_TAEKWON: return BOT_MAPID_TAEKWON; + case JOB_WEDDING: return BOT_MAPID_WEDDING; + case JOB_GUNSLINGER: return BOT_MAPID_GUNSLINGER; + case JOB_NINJA: return BOT_MAPID_NINJA; + case JOB_XMAS: return BOT_MAPID_XMAS; + case JOB_SUMMER: return BOT_MAPID_SUMMER; + case JOB_HANBOK: return BOT_MAPID_HANBOK; + case JOB_GANGSI: return BOT_MAPID_GANGSI; + case JOB_OKTOBERFEST: return BOT_MAPID_OKTOBERFEST; + case JOB_SUMMER2: return BOT_MAPID_SUMMER2; + //2-1 Jobs + case JOB_SUPER_NOVICE: return BOT_MAPID_SUPER_NOVICE; + case JOB_KNIGHT: return BOT_MAPID_KNIGHT; + case JOB_WIZARD: return BOT_MAPID_WIZARD; + case JOB_HUNTER: return BOT_MAPID_HUNTER; + case JOB_PRIEST: return BOT_MAPID_PRIEST; + case JOB_BLACKSMITH: return BOT_MAPID_BLACKSMITH; + case JOB_ASSASSIN: return BOT_MAPID_ASSASSIN; + case JOB_STAR_GLADIATOR: return BOT_MAPID_STAR_GLADIATOR; + case JOB_KAGEROU: + case JOB_OBORO: return BOT_MAPID_KAGEROUOBORO; + case JOB_REBELLION: return BOT_MAPID_REBELLION; + case JOB_DEATH_KNIGHT: return BOT_MAPID_DEATH_KNIGHT; + //2-2 Jobs + case JOB_CRUSADER: return BOT_MAPID_CRUSADER; + case JOB_SAGE: return BOT_MAPID_SAGE; + case JOB_BARD: + case JOB_DANCER: return BOT_MAPID_BARDDANCER; + case JOB_MONK: return BOT_MAPID_MONK; + case JOB_ALCHEMIST: return BOT_MAPID_ALCHEMIST; + case JOB_ROGUE: return BOT_MAPID_ROGUE; + case JOB_SOUL_LINKER: return BOT_MAPID_SOUL_LINKER; + case JOB_DARK_COLLECTOR: return BOT_MAPID_DARK_COLLECTOR; + //Trans Novice And Trans 1-1 Jobs + case JOB_NOVICE_HIGH: return BOT_MAPID_NOVICE_HIGH; + case JOB_SWORDMAN_HIGH: return BOT_MAPID_SWORDMAN_HIGH; + case JOB_MAGE_HIGH: return BOT_MAPID_MAGE_HIGH; + case JOB_ARCHER_HIGH: return BOT_MAPID_ARCHER_HIGH; + case JOB_ACOLYTE_HIGH: return BOT_MAPID_ACOLYTE_HIGH; + case JOB_MERCHANT_HIGH: return BOT_MAPID_MERCHANT_HIGH; + case JOB_THIEF_HIGH: return BOT_MAPID_THIEF_HIGH; + //Trans 2-1 Jobs + case JOB_LORD_KNIGHT: return BOT_MAPID_LORD_KNIGHT; + case JOB_HIGH_WIZARD: return BOT_MAPID_HIGH_WIZARD; + case JOB_SNIPER: return BOT_MAPID_SNIPER; + case JOB_HIGH_PRIEST: return BOT_MAPID_HIGH_PRIEST; + case JOB_WHITESMITH: return BOT_MAPID_WHITESMITH; + case JOB_ASSASSIN_CROSS: return BOT_MAPID_ASSASSIN_CROSS; + //Trans 2-2 Jobs + case JOB_PALADIN: return BOT_MAPID_PALADIN; + case JOB_PROFESSOR: return BOT_MAPID_PROFESSOR; + case JOB_CLOWN: + case JOB_GYPSY: return BOT_MAPID_CLOWNGYPSY; + case JOB_CHAMPION: return BOT_MAPID_CHAMPION; + case JOB_CREATOR: return BOT_MAPID_CREATOR; + case JOB_STALKER: return BOT_MAPID_STALKER; + //Baby Novice And Baby 1-1 Jobs + case JOB_BABY: return BOT_MAPID_BABY; + case JOB_BABY_SWORDMAN: return BOT_MAPID_BABY_SWORDMAN; + case JOB_BABY_MAGE: return BOT_MAPID_BABY_MAGE; + case JOB_BABY_ARCHER: return BOT_MAPID_BABY_ARCHER; + case JOB_BABY_ACOLYTE: return BOT_MAPID_BABY_ACOLYTE; + case JOB_BABY_MERCHANT: return BOT_MAPID_BABY_MERCHANT; + case JOB_BABY_THIEF: return BOT_MAPID_BABY_THIEF; + case JOB_BABY_TAEKWON: return BOT_MAPID_BABY_TAEKWON; + case JOB_BABY_GUNSLINGER: return BOT_MAPID_BABY_GUNSLINGER; + case JOB_BABY_NINJA: return BOT_MAPID_BABY_NINJA; + case JOB_BABY_SUMMONER: return BOT_MAPID_BABY_SUMMONER; + //Baby 2-1 Jobs + case JOB_SUPER_BABY: return BOT_MAPID_SUPER_BABY; + case JOB_BABY_KNIGHT: return BOT_MAPID_BABY_KNIGHT; + case JOB_BABY_WIZARD: return BOT_MAPID_BABY_WIZARD; + case JOB_BABY_HUNTER: return BOT_MAPID_BABY_HUNTER; + case JOB_BABY_PRIEST: return BOT_MAPID_BABY_PRIEST; + case JOB_BABY_BLACKSMITH: return BOT_MAPID_BABY_BLACKSMITH; + case JOB_BABY_ASSASSIN: return BOT_MAPID_BABY_ASSASSIN; + case JOB_BABY_STAR_GLADIATOR: return BOT_MAPID_BABY_STAR_GLADIATOR; + case JOB_BABY_REBELLION: return BOT_MAPID_BABY_REBELLION; + case JOB_BABY_KAGEROU: + case JOB_BABY_OBORO: return BOT_MAPID_BABY_KAGEROUOBORO; + //Baby 2-2 Jobs + case JOB_BABY_CRUSADER: return BOT_MAPID_BABY_CRUSADER; + case JOB_BABY_SAGE: return BOT_MAPID_BABY_SAGE; + case JOB_BABY_BARD: + case JOB_BABY_DANCER: return BOT_MAPID_BABY_BARDDANCER; + case JOB_BABY_MONK: return BOT_MAPID_BABY_MONK; + case JOB_BABY_ALCHEMIST: return BOT_MAPID_BABY_ALCHEMIST; + case JOB_BABY_ROGUE: return BOT_MAPID_BABY_ROGUE; + case JOB_BABY_SOUL_LINKER: return BOT_MAPID_BABY_SOUL_LINKER; + //3-1 Jobs + case JOB_SUPER_NOVICE_E: return BOT_MAPID_SUPER_NOVICE_E; + case JOB_RUNE_KNIGHT: return BOT_MAPID_RUNE_KNIGHT; + case JOB_WARLOCK: return BOT_MAPID_WARLOCK; + case JOB_RANGER: return BOT_MAPID_RANGER; + case JOB_ARCH_BISHOP: return BOT_MAPID_ARCH_BISHOP; + case JOB_MECHANIC: return BOT_MAPID_MECHANIC; + case JOB_GUILLOTINE_CROSS: return BOT_MAPID_GUILLOTINE_CROSS; + case JOB_STAR_EMPEROR: return BOT_MAPID_STAR_EMPEROR; + //3-2 Jobs + case JOB_ROYAL_GUARD: return BOT_MAPID_ROYAL_GUARD; + case JOB_SORCERER: return BOT_MAPID_SORCERER; + case JOB_MINSTREL: + case JOB_WANDERER: return BOT_MAPID_MINSTRELWANDERER; + case JOB_SURA: return BOT_MAPID_SURA; + case JOB_GENETIC: return BOT_MAPID_GENETIC; + case JOB_SHADOW_CHASER: return BOT_MAPID_SHADOW_CHASER; + case JOB_SOUL_REAPER: return BOT_MAPID_SOUL_REAPER; + //Trans 3-1 Jobs + case JOB_RUNE_KNIGHT_T: return BOT_MAPID_RUNE_KNIGHT_T; + case JOB_WARLOCK_T: return BOT_MAPID_WARLOCK_T; + case JOB_RANGER_T: return BOT_MAPID_RANGER_T; + case JOB_ARCH_BISHOP_T: return BOT_MAPID_ARCH_BISHOP_T; + case JOB_MECHANIC_T: return BOT_MAPID_MECHANIC_T; + case JOB_GUILLOTINE_CROSS_T: return BOT_MAPID_GUILLOTINE_CROSS_T; + //Trans 3-2 Jobs + case JOB_ROYAL_GUARD_T: return BOT_MAPID_ROYAL_GUARD_T; + case JOB_SORCERER_T: return BOT_MAPID_SORCERER_T; + case JOB_MINSTREL_T: + case JOB_WANDERER_T: return BOT_MAPID_MINSTRELWANDERER_T; + case JOB_SURA_T: return BOT_MAPID_SURA_T; + case JOB_GENETIC_T: return BOT_MAPID_GENETIC_T; + case JOB_SHADOW_CHASER_T: return BOT_MAPID_SHADOW_CHASER_T; + //Baby 3-1 Jobs + case JOB_SUPER_BABY_E: return BOT_MAPID_SUPER_BABY_E; + case JOB_BABY_RUNE_KNIGHT: return BOT_MAPID_BABY_RUNE_KNIGHT; + case JOB_BABY_WARLOCK: return BOT_MAPID_BABY_WARLOCK; + case JOB_BABY_RANGER: return BOT_MAPID_BABY_RANGER; + case JOB_BABY_ARCH_BISHOP: return BOT_MAPID_BABY_ARCH_BISHOP; + case JOB_BABY_MECHANIC: return BOT_MAPID_BABY_MECHANIC; + case JOB_BABY_GUILLOTINE_CROSS: return BOT_MAPID_BABY_GUILLOTINE_CROSS; + case JOB_BABY_STAR_EMPEROR: return BOT_MAPID_BABY_STAR_EMPEROR; + //Baby 3-2 Jobs + case JOB_BABY_ROYAL_GUARD: return BOT_MAPID_BABY_ROYAL_GUARD; + case JOB_BABY_SORCERER: return BOT_MAPID_BABY_SORCERER; + case JOB_BABY_MINSTREL: + case JOB_BABY_WANDERER: return BOT_MAPID_BABY_MINSTRELWANDERER; + case JOB_BABY_SURA: return BOT_MAPID_BABY_SURA; + case JOB_BABY_GENETIC: return BOT_MAPID_BABY_GENETIC; + case JOB_BABY_SHADOW_CHASER: return BOT_MAPID_BABY_SHADOW_CHASER; + case JOB_BABY_SOUL_REAPER: return BOT_MAPID_BABY_SOUL_REAPER; + //Doram Jobs + case JOB_SUMMONER: return BOT_MAPID_SUMMONER; + case JOB_SPIRIT_HANDLER: return BOT_MAPID_SPIRIT_HANDLER; + //4-1 Jobs + case JOB_HYPER_NOVICE: return BOT_MAPID_HYPER_NOVICE; + case JOB_DRAGON_KNIGHT: return BOT_MAPID_DRAGON_KNIGHT; + case JOB_ARCH_MAGE: return BOT_MAPID_ARCH_MAGE; + case JOB_WINDHAWK: return BOT_MAPID_WINDHAWK; + case JOB_CARDINAL: return BOT_MAPID_CARDINAL; + case JOB_MEISTER: return BOT_MAPID_MEISTER; + case JOB_SHADOW_CROSS: return BOT_MAPID_SHADOW_CROSS; + case JOB_SKY_EMPEROR: return BOT_MAPID_SKY_EMPEROR; + case JOB_NIGHT_WATCH: return BOT_MAPID_NIGHT_WATCH; + case JOB_SHINKIRO: + case JOB_SHIRANUI: return BOT_MAPID_SHINKIRO_SHIRANUI; + //4-2 Jobs + case JOB_IMPERIAL_GUARD: return BOT_MAPID_IMPERIAL_GUARD; + case JOB_ELEMENTAL_MASTER: return BOT_MAPID_ELEMENTAL_MASTER; + case JOB_INQUISITOR: return BOT_MAPID_INQUISITOR; + case JOB_TROUBADOUR: + case JOB_TROUVERE: return BOT_MAPID_TROUBADOURTROUVERE; + case JOB_BIOLO: return BOT_MAPID_BIOLO; + case JOB_ABYSS_CHASER: return BOT_MAPID_ABYSS_CHASER; + case JOB_SOUL_ASCETIC: return BOT_MAPID_SOUL_ASCETIC; + //Unknown + default: + return -1; + } +} + +const std::string BotJobDatabase::getDefaultLocation() { + return std::string(db_path) + "/job_stats.yml"; +} + +/** + * Returns base experience for this character's class. + * @param job_id: Player's class + * @param level: Player's level + * @return Base EXP + */ +t_exp BotJobDatabase::get_baseExp(uint16 job_id, uint32 level) { + std::shared_ptr job = bot_job_db.find(job_id); + + return job ? job->base_exp[level - 1] : 0; +} + +/** + * Returns job experience for this character's class. + * @param job_id: Player's class + * @param level: Player's level + * @return Job EXP + */ +t_exp BotJobDatabase::get_jobExp(uint16 job_id, uint32 level) { + std::shared_ptr job = bot_job_db.find(job_id); + + return job ? job->job_exp[level - 1] : 0; +} + +/** + * Reads and parses an entry from the job_db. + * @param node: YAML node containing the entry. + * @return count of successfully parsed rows + */ +uint64 BotJobDatabase::parseBodyNode(const ryml::NodeRef& node) { + if (this->nodeExists(node, "Jobs")) { + const ryml::NodeRef& jobsNode = node["Jobs"]; + + for (const auto& jobit : jobsNode) { + std::string job_name; + c4::from_chars(jobit.key(), &job_name); + std::string job_name_constant = "JOB_" + job_name; + int64 job_id = convert_jobid_to_number(job_name_constant.c_str()); + + //if (!script_get_constant(job_name_constant.c_str(), &job_id)) { + // this->invalidWarning(node["Job"], "Job %s does not exist.\n", job_name.c_str()); + // return 0; + //} + + std::shared_ptr job = bot_job_db.find(static_cast(job_id)); + bool exists = job != nullptr; + + if (!exists) { + job = std::make_shared(); + + job->job_bonus.resize(MAX_LEVEL); + std::fill(job->job_bonus.begin(), job->job_bonus.end(), std::array { 0 }); + + job->base_hp.resize(MAX_LEVEL); + std::fill(job->base_hp.begin(), job->base_hp.end(), 0); + + job->base_sp.resize(MAX_LEVEL); + std::fill(job->base_sp.begin(), job->base_sp.end(), 0); + + job->base_ap.resize(MAX_LEVEL); + std::fill(job->base_ap.begin(), job->base_ap.end(), 0); + } + + if (this->nodeExists(node, "MaxWeight")) { + uint32 weight; + + if (!this->asUInt32(node, "MaxWeight", weight)) + return 0; + + job->max_weight_base = weight; + } + else { + if (!exists) + job->max_weight_base = 20000; + } + + if (this->nodeExists(node, "HpFactor")) { + uint32 hp; + + if (!this->asUInt32(node, "HpFactor", hp)) + return 0; + + job->hp_factor = hp; + } + else { + if (!exists) + job->hp_factor = 0; + } + + if (this->nodeExists(node, "HpIncrease")) { + uint32 hp; + + if (!this->asUInt32(node, "HpIncrease", hp)) + return 0; + + job->hp_increase = hp; + } + else { + if (!exists) + job->hp_increase = 500; + } + + if (this->nodeExists(node, "SpIncrease")) { + uint32 sp; + + if (!this->asUInt32(node, "SpIncrease", sp)) + return 0; + + job->sp_increase = sp; + } + else { + if (!exists) + job->sp_increase = 100; + } + + if (this->nodeExists(node, "BaseASPD")) { + const ryml::NodeRef& aspdNode = node["BaseASPD"]; + uint8 max = BOT_MAX_WEAPON_TYPE; + +#ifdef RENEWAL // Renewal adds an extra column for shields + max += 1; +#endif + + if (!exists) { + job->aspd_base.resize(max); + std::fill(job->aspd_base.begin(), job->aspd_base.end(), 2000); + } + + for (const auto& aspdit : aspdNode) { + std::string weapon; + c4::from_chars(aspdit.key(), &weapon); + std::string weapon_constant = "W_" + weapon; + int64 constant = convert_weaponid_to_number(weapon_constant.c_str()); + + //if (!script_get_constant(weapon_constant.c_str(), &constant)) { + // this->invalidWarning(aspdNode["BaseASPD"], "Unknown weapon type %s specified for %s, skipping.\n", weapon.c_str(), job_name.c_str()); + // continue; + //} + + if (constant < BOT_W_FIST || constant >= max) { + this->invalidWarning(aspdNode["BaseASPD"], "Invalid weapon type %s specified for %s, skipping.\n", weapon.c_str(), job_name.c_str()); + continue; + } + + int16 aspd; + + if (!this->asInt16(aspdNode, weapon.c_str(), aspd)) + return 0; + + job->aspd_base[static_cast(constant)] = aspd; + } + } + else { + if (!exists) { + uint8 max = BOT_MAX_WEAPON_TYPE; + +#ifdef RENEWAL // Renewal adds an extra column for shields + max += 1; +#endif + job->aspd_base.resize(max); + std::fill(job->aspd_base.begin(), job->aspd_base.end(), 2000); + } + } + + if (this->nodeExists(node, "MaxStats")) { + const ryml::NodeRef& statNode = node["MaxStats"]; + + for (const auto& statit : statNode) { + std::string stat; + c4::from_chars(statit.key(), &stat); + std::string stat_constant = "PARAM_" + stat; + int64 constant; + + //if (!script_get_constant(stat_constant.c_str(), &constant)) { + // this->invalidWarning(statNode["Bonus"], "Unknown max stat %s specified for %s, skipping.\n", stat.c_str(), job_name.c_str()); + // continue; + //} + + if (constant < BOT_PARAM_STR || constant >= BOT_PARAM_MAX) { + this->invalidWarning(statNode["Bonus"], "Invalid max stat %s specified for %s, skipping.\n", stat.c_str(), job_name.c_str()); + continue; + } + + uint16 max; + + if (!this->asUInt16(statNode, stat.c_str(), max)) + return 0; + + job->max_param[constant] = max; + } + } + + if (this->nodeExists(node, "MaxBaseLevel")) { + uint16 level; + + if (!this->asUInt16(node, "MaxBaseLevel", level)) + return 0; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(node["MaxBaseLevel"], "MaxBaseLevel must be between 1~MAX_LEVEL for %s, capping to MAX_LEVEL.\n", job_name.c_str()); + level = MAX_LEVEL; + } + + job->max_base_level = level; + } + else { + if (!exists) + job->max_base_level = MAX_LEVEL; + } + + if (this->nodeExists(node, "BaseExp")) { + for (const ryml::NodeRef& bexpNode : node["BaseExp"]) { + uint16 level; + + if (!this->asUInt16(bexpNode, "Level", level)) + return 0; + + if (level > job->max_base_level) + continue; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(bexpNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + if (this->nodeExists(bexpNode, "Exp")) { + t_exp exp; + + if (!this->asUInt64(bexpNode, "Exp", exp)) + return 0; + + job->base_exp[level - 1] = exp; + } + } + } + + if (this->nodeExists(node, "MaxJobLevel")) { + uint16 level; + + if (!this->asUInt16(node, "MaxJobLevel", level)) + return 0; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(node["MaxJobLevel"], "MaxJobLevel must be between 1~MAX_LEVEL for %s, capping to MAX_LEVEL.\n", job_name.c_str()); + level = MAX_LEVEL; + } + + job->max_job_level = level; + } + else { + if (!exists) + job->max_job_level = MAX_LEVEL; + } + + if (this->nodeExists(node, "JobExp")) { + for (const ryml::NodeRef& jexpNode : node["JobExp"]) { + uint16 level; + + if (!this->asUInt16(jexpNode, "Level", level)) + return 0; + + if (level > job->max_job_level) + continue; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(jexpNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + if (this->nodeExists(jexpNode, "Exp")) { + t_exp exp; + + if (!this->asUInt64(jexpNode, "Exp", exp)) + return 0; + + job->job_exp[level - 1] = exp; + } + } + } + + if (this->nodeExists(node, "BonusStats")) { + const ryml::NodeRef& bonusNode = node["BonusStats"]; + + for (const ryml::NodeRef& levelNode : bonusNode) { + uint16 level; + + if (!this->asUInt16(levelNode, "Level", level)) + return 0; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(levelNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + for (uint8 idx = BOT_PARAM_STR; idx < BOT_PARAM_MAX; idx++) { + if (this->nodeExists(levelNode, bot_parameter_names[idx])) { + int16 change; + + if (!this->asInt16(levelNode, bot_parameter_names[idx], change)) + return 0; + + job->job_bonus[level - 1][idx] = change; + } + } + } + } + +#ifdef HP_SP_TABLES + if (this->nodeExists(node, "BaseHp")) { + for (const ryml::NodeRef& bhpNode : node["BaseHp"]) { + uint16 level; + + if (!this->asUInt16(bhpNode, "Level", level)) + return 0; + + if (level > job->max_base_level) + continue; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(bhpNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + if (this->nodeExists(bhpNode, "Hp")) { + uint32 points; + + if (!this->asUInt32(bhpNode, "Hp", points)) + return 0; + + job->base_hp[level - 1] = points; + } + } + } + + if (this->nodeExists(node, "BaseSp")) { + for (const ryml::NodeRef& bspNode : node["BaseSp"]) { + uint16 level; + + if (!this->asUInt16(bspNode, "Level", level)) + return 0; + + if (level > job->max_base_level) + continue; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(bspNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + if (this->nodeExists(bspNode, "Sp")) { + uint32 points; + + if (!this->asUInt32(bspNode, "Sp", points)) + return 0; + + job->base_sp[level - 1] = points; + } + } + } + + if (this->nodeExists(node, "BaseAp")) { + for (const ryml::NodeRef& bapNode : node["BaseAp"]) { + uint16 level; + + if (!this->asUInt16(bapNode, "Level", level)) + return 0; + + if (level > job->max_base_level) + continue; + + if (level == 0 || level > MAX_LEVEL) { + this->invalidWarning(bapNode["Level"], "Level must be between 1~MAX_LEVEL for %s.\n", job_name.c_str()); + return 0; + } + + if (this->nodeExists(bapNode, "Ap")) { + uint32 points; + + if (!this->asUInt32(bapNode, "Ap", points)) + return 0; + + job->base_ap[level - 1] = points; + } + } + } +#endif + + if (!exists) + this->put(static_cast(job_id), job); + } + } + + return 1; +} + +void BotJobDatabase::loadingFinished() { + // Checking if all class have their data + for (auto& jobIt : *this) { + uint16 job_id = jobIt.first; + + if (!pcdb_checkid(job_id)) + continue; + if (job_id == JOB_WEDDING || job_id == JOB_XMAS || job_id == JOB_SUMMER || job_id == JOB_HANBOK || job_id == JOB_OKTOBERFEST || job_id == JOB_SUMMER2) + continue; // Classes that do not need exp tables. + + std::shared_ptr job = jobIt.second; + uint16 maxBaseLv = job->max_base_level, maxJobLv = job->max_job_level; + + //if (!maxBaseLv) + // ShowWarning("Class %s (%d) does not have a base exp table.\n", job_name(job_id), job_id); + //if (!maxJobLv) + // ShowWarning("Class %s (%d) does not have a job exp table.\n", job_name(job_id), job_id); + + // Init and checking the empty value of Base HP/SP [Cydh] + if (job->base_hp.empty()) + job->base_hp.resize(maxBaseLv); + for (uint16 j = 0; j < maxBaseLv; j++) { + if (job->base_hp[j] == 0) + job->base_hp[j] = pc_calc_basehp(j + 1, job_id); + } + if (job->base_sp.empty()) + job->base_sp.resize(maxBaseLv); + for (uint16 j = 0; j < maxBaseLv; j++) { + if (job->base_sp[j] == 0) + job->base_sp[j] = pc_calc_basesp(j + 1, job_id); + } + + // Resize to the maximum base level + if (job->base_hp.capacity() > maxBaseLv) + job->base_hp.erase(job->base_hp.begin() + maxBaseLv, job->base_hp.end()); + if (job->base_sp.capacity() > maxBaseLv) + job->base_sp.erase(job->base_sp.begin() + maxBaseLv, job->base_sp.end()); + if (job->base_ap.capacity() > maxBaseLv) + job->base_ap.erase(job->base_ap.begin() + maxBaseLv, job->base_ap.end()); + + // Resize to the maximum job level + if (job->job_bonus.capacity() > maxJobLv) + job->job_bonus.erase(job->job_bonus.begin() + maxJobLv, job->job_bonus.end()); + + for (uint16 parameter = BOT_PARAM_STR; parameter < BOT_PARAM_MAX; parameter++) { + // Store total + int16 current = 0; + + for (uint16 job_level = 0; job_level < maxJobLv; job_level++) { + // Add the bonus from this job level + current += job->job_bonus[job_level][parameter]; + + // Set the new total on this job level + job->job_bonus[job_level][parameter] = current; + } + } + + uint64 class_ = pc_jobid2mapid(job_id); + + + // Set normal status limits + uint16 max = 99;//battle_config.max_parameter; + + do { + // Always check babies first + if (class_ & JOBL_BABY) { + if (class_ & JOBL_THIRD) { + max = 108;// battle_config.max_baby_third_parameter; + break; + } + else { + max = 80;// battle_config.max_baby_parameter; + break; + } + } + + // Summoner + if ((class_ & MAPID_BASEMASK) == BOT_MAPID_SUMMONER) { + max = 120;//battle_config.max_summoner_parameter; + break; + } + + // Extended classes + if ((class_ & MAPID_UPPERMASK) == BOT_MAPID_KAGEROUOBORO || (class_ & MAPID_UPPERMASK) == BOT_MAPID_REBELLION) { + max = 125;// battle_config.max_extended_parameter; + break; + } + + if (class_ & JOBL_FOURTH) { + max = 135;// battle_config.max_fourth_parameter; + break; + } + + // 3rd class + if (class_ & JOBL_THIRD) { + // Transcendent + if (class_ & JOBL_UPPER) { + max = 135;// battle_config.max_third_trans_parameter; + break; + } + else { + max = 135;// battle_config.max_third_parameter; + break; + } + } + + // Transcendent + if (class_ & JOBL_UPPER) { + max = 99;//battle_config.max_trans_parameter; + break; + } + } while (false); + + for (uint16 parameter = BOT_PARAM_STR; parameter < BOT_PARAM_POW; parameter++) { + // If it is not explicitly set in the database file + if (job->max_param[parameter] == 0) { + job->max_param[parameter] = max; + } + } + + // Set trait status limit + if (class_ & JOBL_FOURTH) { + max = 100;// battle_config.max_trait_parameter; + } + else { + max = 0; + } + + for (uint16 parameter = BOT_PARAM_POW; parameter < BOT_PARAM_MAX; parameter++) { + // If it is not explicitly set in the database file + if (job->max_param[parameter] == 0) { + job->max_param[parameter] = max; + } + } + } + + TypesafeCachedYamlDatabase::loadingFinished(); +} + +/*========================================== + * DB reading. + * job_stats.yml - Job values + * skill_tree.txt - skill tree for every class + * attr_fix.yml - elemental adjustment table + *------------------------------------------*/ +void bot_read_yaml_dbs(void) { + int i, s = 1; + const char* dbsubpath[] = { + "", + "/" DBIMPORT, + //add other path here + }; + + //reset + bot_job_db.clear(); // job_info table + +//if defined(RENEWAL_DROP) || defined(RENEWAL_EXP) +// penalty_db.load(); +//endif + + bot_statpoint_db.clear(); + bot_job_db.load(); + + for (i = 0; i < ARRAYLENGTH(dbsubpath); i++) { + uint8 n1 = (uint8)(strlen(db_path) + strlen(dbsubpath[i]) + 1); + uint8 n2 = (uint8)(strlen(db_path) + strlen(DBPATH) + strlen(dbsubpath[i]) + 1); + char* dbsubpath1 = (char*)aMalloc(n1 + 1); + char* dbsubpath2 = (char*)aMalloc(n2 + 1); + + if (i == 0) { + safesnprintf(dbsubpath1, n1, "%s%s", db_path, dbsubpath[i]); + safesnprintf(dbsubpath2, n2, "%s/%s%s", db_path, DBPATH, dbsubpath[i]); + } + else { + safesnprintf(dbsubpath1, n1, "%s%s", db_path, dbsubpath[i]); + safesnprintf(dbsubpath2, n1, "%s%s", db_path, dbsubpath[i]); + } + + //sv_readdb(dbsubpath2, "job_noenter_map.txt", ',', 3, 3, CLASS_COUNT, &pc_readdb_job_noenter_map, i > 0); + aFree(dbsubpath1); + aFree(dbsubpath2); } + + // Reset and read skilltree - needs to be read after pc_readdb_job_exp to get max base and job levels + //skill_tree_db.reload(); + + bot_statpoint_db.load(); +} diff --git a/src/bot/Brokk/char_creation_helpers.hpp b/src/bot/Brokk/char_creation_helpers.hpp new file mode 100644 index 0000000..31f26d2 --- /dev/null +++ b/src/bot/Brokk/char_creation_helpers.hpp @@ -0,0 +1,369 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef CHAR_CREATION_HELPERS_HPP +#define CHAR_CREATION_HELPERS_HPP + +#include +#include +#include +#include +#include // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus +#include "charakters.hpp" +/* +* Constants for yaml dbs +*/ +#define MAX_LEVEL 275 + +/// Enum of Player's Parameter +enum bot_params { + BOT_PARAM_STR = 0, + BOT_PARAM_AGI, + BOT_PARAM_VIT, + BOT_PARAM_INT, + BOT_PARAM_DEX, + BOT_PARAM_LUK, + BOT_PARAM_POW, + BOT_PARAM_STA, + BOT_PARAM_WIS, + BOT_PARAM_SPL, + BOT_PARAM_CON, + BOT_PARAM_CRT, + BOT_PARAM_MAX +}; + +static const char* bot_parameter_names[BOT_PARAM_MAX] = { + "Str", + "Agi", + "Vit", + "Int", + "Dex", + "Luk", + "Pow", + "Sta", + "Wis", + "Spl", + "Con", + "Crt" +}; + +//Total number of classes (for data storage) +#define CLASS_COUNT (JOB_MAX - JOB_NOVICE_HIGH + JOB_MAX_BASIC) + +enum bot_weapon_type : uint8 { + BOT_W_FIST, //Bare hands + BOT_W_DAGGER, //1 + BOT_W_1HSWORD, //2 + BOT_W_2HSWORD, //3 + BOT_W_1HSPEAR, //4 + BOT_W_2HSPEAR, //5 + BOT_W_1HAXE, //6 + BOT_W_2HAXE, //7 + BOT_W_MACE, //8 + BOT_W_2HMACE, //9 (unused) + BOT_W_STAFF, //10 + BOT_W_BOW, //11 + BOT_W_KNUCKLE, //12 + BOT_W_MUSICAL, //13 + BOT_W_WHIP, //14 + BOT_W_BOOK, //15 + BOT_W_KATAR, //16 + BOT_W_REVOLVER, //17 + BOT_W_RIFLE, //18 + BOT_W_GATLING, //19 + BOT_W_SHOTGUN, //20 + BOT_W_GRENADE, //21 + BOT_W_HUUMA, //22 + BOT_W_2HSTAFF, //23 + BOT_MAX_WEAPON_TYPE, + // dual-wield constants + BOT_W_DOUBLE_DD, // 2 daggers + BOT_W_DOUBLE_SS, // 2 swords + BOT_W_DOUBLE_AA, // 2 axes + BOT_W_DOUBLE_DS, // dagger + sword + BOT_W_DOUBLE_DA, // dagger + axe + BOT_W_DOUBLE_SA, // sword + axe + BOT_MAX_WEAPON_TYPE_ALL, + BOT_W_SHIELD = BOT_MAX_WEAPON_TYPE, +}; + +#define WEAPON_TYPE_ALL ((1<= JOB_NOVICE_HIGH && (class_) <= JOB_DARK_COLLECTOR ) || \ + ( (class_) >= JOB_RUNE_KNIGHT && (class_) <= JOB_MECHANIC_T2 ) || \ + ( (class_) >= JOB_BABY_RUNE_KNIGHT && (class_) <= JOB_BABY_MECHANIC2 ) || \ + ( (class_) >= JOB_SUPER_NOVICE_E && (class_) <= JOB_SUPER_BABY_E ) || \ + ( (class_) >= JOB_KAGEROU && (class_) <= JOB_OBORO ) || \ + (class_) == JOB_REBELLION || (class_) == JOB_SUMMONER || \ + (class_) == JOB_BABY_SUMMONER || \ + ( (class_) >= JOB_BABY_NINJA && (class_) <= JOB_BABY_REBELLION ) || \ + ( (class_) >= JOB_BABY_STAR_GLADIATOR2 && (class_) <= JOB_BABY_STAR_EMPEROR2 ) || \ + ( (class_) >= JOB_DRAGON_KNIGHT && (class_) <= JOB_TROUVERE ) || \ + ( (class_) >= JOB_WINDHAWK2 && (class_) <= JOB_IMPERIAL_GUARD2 ) || \ + ( (class_) >= JOB_SKY_EMPEROR && (class_) <= JOB_SPIRIT_HANDLER ) || \ + (class_) == JOB_SKY_EMPEROR2 \ +) +#define pcdb_checkid(class_) pcdb_checkid_sub((unsigned int)class_) + + +//These marks the "level" of the job. +#define JOBL_2_1 0x100 //256 +#define JOBL_2_2 0x200 //512 +#define JOBL_2 0x300 //768 + +#define JOBL_UPPER 0x1000 //4096 +#define JOBL_BABY 0x2000 //8192 +#define JOBL_THIRD 0x4000 //16384 +#define JOBL_FOURTH 0x8000 //32768 + +//First Jobs +//Note the oddity of the novice: + +enum bot_mapid : uint64{ +//Novice And 1-1 Jobs + BOT_MAPID_NOVICE = 0x0, + BOT_MAPID_SWORDMAN, + BOT_MAPID_MAGE, + BOT_MAPID_ARCHER, + BOT_MAPID_ACOLYTE, + BOT_MAPID_MERCHANT, + BOT_MAPID_THIEF, + BOT_MAPID_TAEKWON, + BOT_MAPID_WEDDING, + BOT_MAPID_GUNSLINGER, + BOT_MAPID_NINJA, + BOT_MAPID_XMAS, + BOT_MAPID_SUMMER, + BOT_MAPID_HANBOK, + BOT_MAPID_GANGSI, + BOT_MAPID_OKTOBERFEST, + BOT_MAPID_SUMMONER, + BOT_MAPID_SUMMER2, +//2-1 Jobs + BOT_MAPID_SUPER_NOVICE = JOBL_2_1|BOT_MAPID_NOVICE, + BOT_MAPID_KNIGHT, + BOT_MAPID_WIZARD, + BOT_MAPID_HUNTER, + BOT_MAPID_PRIEST, + BOT_MAPID_BLACKSMITH, + BOT_MAPID_ASSASSIN, + BOT_MAPID_STAR_GLADIATOR, + BOT_MAPID_REBELLION = JOBL_2_1| BOT_MAPID_GUNSLINGER, + BOT_MAPID_KAGEROUOBORO, + BOT_MAPID_DEATH_KNIGHT = JOBL_2_1|BOT_MAPID_GANGSI, +//2-2 Jobs + BOT_MAPID_CRUSADER = JOBL_2_2|BOT_MAPID_SWORDMAN, + BOT_MAPID_SAGE, + BOT_MAPID_BARDDANCER, + BOT_MAPID_MONK, + BOT_MAPID_ALCHEMIST, + BOT_MAPID_ROGUE, + BOT_MAPID_SOUL_LINKER, + BOT_MAPID_DARK_COLLECTOR = JOBL_2_2|BOT_MAPID_GANGSI, +//Trans Novice And Trans 1-1 Jobs + BOT_MAPID_NOVICE_HIGH = JOBL_UPPER|BOT_MAPID_NOVICE, + BOT_MAPID_SWORDMAN_HIGH, + BOT_MAPID_MAGE_HIGH, + BOT_MAPID_ARCHER_HIGH, + BOT_MAPID_ACOLYTE_HIGH, + BOT_MAPID_MERCHANT_HIGH, + BOT_MAPID_THIEF_HIGH, +//Trans 2-1 Jobs + BOT_MAPID_LORD_KNIGHT = JOBL_UPPER|BOT_MAPID_KNIGHT, + BOT_MAPID_HIGH_WIZARD, + BOT_MAPID_SNIPER, + BOT_MAPID_HIGH_PRIEST, + BOT_MAPID_WHITESMITH, + BOT_MAPID_ASSASSIN_CROSS, +//Trans 2-2 Jobs + BOT_MAPID_PALADIN = JOBL_UPPER|BOT_MAPID_CRUSADER, + BOT_MAPID_PROFESSOR, + BOT_MAPID_CLOWNGYPSY, + BOT_MAPID_CHAMPION, + BOT_MAPID_CREATOR, + BOT_MAPID_STALKER, +//Baby Novice And Baby 1-1 Jobs + BOT_MAPID_BABY = JOBL_BABY|BOT_MAPID_NOVICE, + BOT_MAPID_BABY_SWORDMAN, + BOT_MAPID_BABY_MAGE, + BOT_MAPID_BABY_ARCHER, + BOT_MAPID_BABY_ACOLYTE, + BOT_MAPID_BABY_MERCHANT, + BOT_MAPID_BABY_THIEF, + BOT_MAPID_BABY_TAEKWON, + BOT_MAPID_BABY_GUNSLINGER = JOBL_BABY|BOT_MAPID_GUNSLINGER, + BOT_MAPID_BABY_NINJA, + BOT_MAPID_BABY_SUMMONER = JOBL_BABY|BOT_MAPID_SUMMONER, +//Baby 2-1 Jobs + BOT_MAPID_SUPER_BABY = JOBL_BABY|BOT_MAPID_SUPER_NOVICE, + BOT_MAPID_BABY_KNIGHT, + BOT_MAPID_BABY_WIZARD, + BOT_MAPID_BABY_HUNTER, + BOT_MAPID_BABY_PRIEST, + BOT_MAPID_BABY_BLACKSMITH, + BOT_MAPID_BABY_ASSASSIN, + BOT_MAPID_BABY_STAR_GLADIATOR, + BOT_MAPID_BABY_REBELLION = JOBL_BABY|BOT_MAPID_REBELLION, + BOT_MAPID_BABY_KAGEROUOBORO, +//Baby 2-2 Jobs + BOT_MAPID_BABY_CRUSADER = JOBL_BABY|BOT_MAPID_CRUSADER, + BOT_MAPID_BABY_SAGE, + BOT_MAPID_BABY_BARDDANCER, + BOT_MAPID_BABY_MONK, + BOT_MAPID_BABY_ALCHEMIST, + BOT_MAPID_BABY_ROGUE, + BOT_MAPID_BABY_SOUL_LINKER, +//3-1 Jobs + BOT_MAPID_SUPER_NOVICE_E = JOBL_THIRD|BOT_MAPID_SUPER_NOVICE, + BOT_MAPID_RUNE_KNIGHT, + BOT_MAPID_WARLOCK, + BOT_MAPID_RANGER, + BOT_MAPID_ARCH_BISHOP, + BOT_MAPID_MECHANIC, + BOT_MAPID_GUILLOTINE_CROSS, + BOT_MAPID_STAR_EMPEROR, +//3-2 Jobs + BOT_MAPID_ROYAL_GUARD = JOBL_THIRD|BOT_MAPID_CRUSADER, + BOT_MAPID_SORCERER, + BOT_MAPID_MINSTRELWANDERER, + BOT_MAPID_SURA, + BOT_MAPID_GENETIC, + BOT_MAPID_SHADOW_CHASER, + BOT_MAPID_SOUL_REAPER, +//Trans 3-1 Jobs + BOT_MAPID_RUNE_KNIGHT_T = JOBL_THIRD|BOT_MAPID_LORD_KNIGHT, + BOT_MAPID_WARLOCK_T, + BOT_MAPID_RANGER_T, + BOT_MAPID_ARCH_BISHOP_T, + BOT_MAPID_MECHANIC_T, + BOT_MAPID_GUILLOTINE_CROSS_T, +//Trans 3-2 Jobs + BOT_MAPID_ROYAL_GUARD_T = JOBL_THIRD|BOT_MAPID_PALADIN, + BOT_MAPID_SORCERER_T, + BOT_MAPID_MINSTRELWANDERER_T, + BOT_MAPID_SURA_T, + BOT_MAPID_GENETIC_T, + BOT_MAPID_SHADOW_CHASER_T, +//Baby 3-1 Jobs + BOT_MAPID_SUPER_BABY_E = JOBL_THIRD|BOT_MAPID_SUPER_BABY, + BOT_MAPID_BABY_RUNE_KNIGHT, + BOT_MAPID_BABY_WARLOCK, + BOT_MAPID_BABY_RANGER, + BOT_MAPID_BABY_ARCH_BISHOP, + BOT_MAPID_BABY_MECHANIC, + BOT_MAPID_BABY_GUILLOTINE_CROSS, + BOT_MAPID_BABY_STAR_EMPEROR, +//Baby 3-2 Jobs + BOT_MAPID_BABY_ROYAL_GUARD = JOBL_THIRD|BOT_MAPID_BABY_CRUSADER, + BOT_MAPID_BABY_SORCERER, + BOT_MAPID_BABY_MINSTRELWANDERER, + BOT_MAPID_BABY_SURA, + BOT_MAPID_BABY_GENETIC, + BOT_MAPID_BABY_SHADOW_CHASER, + BOT_MAPID_BABY_SOUL_REAPER, +//4-1 Jobs + BOT_MAPID_HYPER_NOVICE = JOBL_FOURTH|JOBL_THIRD|JOBL_UPPER|BOT_MAPID_SUPER_NOVICE, + BOT_MAPID_DRAGON_KNIGHT, + BOT_MAPID_ARCH_MAGE, + BOT_MAPID_WINDHAWK, + BOT_MAPID_CARDINAL, + BOT_MAPID_MEISTER, + BOT_MAPID_SHADOW_CROSS, + BOT_MAPID_SKY_EMPEROR, + BOT_MAPID_NIGHT_WATCH = JOBL_FOURTH|JOBL_THIRD|JOBL_UPPER|BOT_MAPID_REBELLION, + BOT_MAPID_SHINKIRO_SHIRANUI, + BOT_MAPID_SPIRIT_HANDLER = JOBL_FOURTH|JOBL_THIRD|JOBL_UPPER|JOBL_2_1|BOT_MAPID_SUMMONER, +//4-2 Jobs + BOT_MAPID_IMPERIAL_GUARD = JOBL_FOURTH|JOBL_THIRD|JOBL_UPPER|BOT_MAPID_CRUSADER, + BOT_MAPID_ELEMENTAL_MASTER, + BOT_MAPID_TROUBADOURTROUVERE, + BOT_MAPID_INQUISITOR, + BOT_MAPID_BIOLO, + BOT_MAPID_ABYSS_CHASER, + BOT_MAPID_SOUL_ASCETIC, +// Additional constants + BOT_MAPID_ALL = UINT64_MAX +}; + + +//for filtering and quick checking. +#define MAPID_BASEMASK 0x00ff +#define MAPID_UPPERMASK 0x0fff +#define MAPID_THIRDMASK (JOBL_THIRD|MAPID_UPPERMASK) +#define MAPID_FOURTHMASK (JOBL_FOURTH|MAPID_THIRDMASK|JOBL_UPPER) + +/* +* Yaml DBs and their containers +*/ +struct bot_statpoint_entry { + uint16 level; + uint32 statpoints; + uint32 traitpoints; +}; + +class BotStatPointDatabase : public TypesafeCachedYamlDatabase { +public: + BotStatPointDatabase() : TypesafeCachedYamlDatabase("STATPOINT_DB", 2, 1) { + + } + + const std::string getDefaultLocation() override; + uint64 parseBodyNode(const ryml::NodeRef& node) override; + void loadingFinished() override; + + // Additional + uint32 load_max_status_points_of_level(uint16 level); + uint32 get_table_point(uint16 level); + uint32 load_max_trait_points_of_level(uint16 level); + uint32 get_trait_table_point(uint16 level); +}; + +extern BotStatPointDatabase bot_statpoint_db; + +struct bot_job_info { + std::vector base_hp, base_sp, base_ap; //Storage for the first calculation with hp/sp/ap factor and multiplicator + uint32 hp_factor, hp_increase, sp_increase, max_weight_base; + std::vector> job_bonus; + std::vector aspd_base; + t_exp base_exp[MAX_LEVEL], job_exp[MAX_LEVEL]; + uint16 max_base_level, max_job_level; + uint16 max_param[BOT_PARAM_MAX]; + struct s_job_noenter_map { + uint32 zone; + uint8 group_lv; + } noenter_map; +}; + +class BotJobDatabase : public TypesafeCachedYamlDatabase { +public: + BotJobDatabase() : TypesafeCachedYamlDatabase("JOB_STATS", 2) { + + } + + const std::string getDefaultLocation() override; + uint64 parseBodyNode(const ryml::NodeRef& node) override; + void loadingFinished() override; + + // Additional + uint32 get_maxBaseLv(uint16 job_id); + uint32 get_maxJobLv(uint16 job_id); + t_exp get_baseExp(uint16 job_id, uint32 level); + t_exp get_jobExp(uint16 job_id, uint32 level); + int32 get_maxWeight(uint16 job_id); +}; + +extern BotJobDatabase bot_job_db; + +/* +* Tool Functions +*/ + +void bot_read_yaml_dbs(void); + +#endif /* CHAR_CREATION_HELPERS_HPP */ diff --git a/src/bot/Brokk/charakters.cpp b/src/bot/Brokk/charakters.cpp new file mode 100644 index 0000000..0e2a0b6 --- /dev/null +++ b/src/bot/Brokk/charakters.cpp @@ -0,0 +1,1107 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + + +#include "charakters.hpp" + +#include //min / max +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "brokk.hpp" // brokk_config + +/// global defines + +/// internal structure +typedef struct CharDB_SQL { + CharDB vtable; // public interface + Sql* charakters; // SQL handle charakters storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = "ragnarok"; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char char_db[32]; + +} CharDB_SQL; + +/// internal structure +typedef struct CharakterDBIterator_SQL { + CharakterDBIterator vtable; // public interface + CharDB_SQL* db; + int last_char_id; +} CharakterDBIterator_SQL; + +/// internal functions +static bool char_db_sql_init(CharDB* self); +static void char_db_sql_destroy(CharDB* self); +static bool char_db_sql_get_property(CharDB* self, const char* key, char* buf, size_t buflen); +static bool char_db_sql_set_property(CharDB* self, const char* option, const char* value); +static bool char_db_sql_create(CharDB* self, struct mmo_charakter* charakter); +static bool char_db_sql_remove(CharDB* self, const uint32 char_id); +static bool char_db_sql_save(CharDB* self, const struct mmo_charakter* charakter); +static bool char_db_sql_load_num(CharDB* self, struct mmo_charakter* charakter, const uint32 char_id); +static bool char_db_sql_load_str(CharDB* self, struct mmo_charakter* charakter, const char* char_name); +static CharakterDBIterator* char_db_sql_iterator(CharDB* self); +static void char_db_sql_iter_destroy(CharakterDBIterator* self); +static bool char_db_sql_iter_next(CharakterDBIterator* self, struct mmo_charakter* charakter); +static bool mmo_char_auth_fromsql(CharDB_SQL* db, struct mmo_charakter* charakter, uint32 char_id); +static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* charakter, bool is_new); + +/// public constructor +CharDB* char_db_sql(void) { + CharDB_SQL* db = (CharDB_SQL*)aCalloc(1, sizeof(CharDB_SQL)); + new(db) CharDB_SQL(); + + // set up the vtable + db->vtable.init = &char_db_sql_init; + db->vtable.destroy = &char_db_sql_destroy; + db->vtable.get_property = &char_db_sql_get_property; + db->vtable.set_property = &char_db_sql_set_property; + db->vtable.save = &char_db_sql_save; + db->vtable.create = &char_db_sql_create; + db->vtable.remove = &char_db_sql_remove; + db->vtable.load_num = &char_db_sql_load_num; + db->vtable.load_str = &char_db_sql_load_str; + db->vtable.iterator = &char_db_sql_iterator; + + // initialize to default values + db->charakters = nullptr; + + // other settings + db->case_sensitive = false; + safestrncpy(db->char_db, "char", sizeof(db->char_db)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool char_db_sql_init(CharDB* self) { + CharDB_SQL* db = (CharDB_SQL*)self; + Sql* sql_handle; + + db->charakters = Sql_Malloc(); + sql_handle = db->charakters; + + if (SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str())) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->charakters); + db->charakters = nullptr; + return false; + } + + if (!db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str())) + Sql_ShowDebug(sql_handle); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void char_db_sql_destroy(CharDB* self){ + CharDB_SQL* db = (CharDB_SQL*)self; + + Sql_Free(db->charakters); + db->charakters = nullptr; + + db->~CharDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +static bool char_db_sql_get_property(CharDB* self, const char* key, char* buf, size_t buflen) +{ + CharDB_SQL* db = (CharDB_SQL*)self; + const char* signature; + + signature = "char_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "char_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->char_db); + else + return false;// not found + return true; + } + + signature = "char_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool char_db_sql_set_property(CharDB* self, const char* key, const char* value) { + CharDB_SQL* db = (CharDB_SQL*)self; + const char* signature; + + signature = "char_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "char_db") == 0 ) + safestrncpy(db->char_db, value, sizeof(db->char_db)); + else + return false;// not found + return true; + } + + signature = "char_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Create a new charakter entry. + * charakter->char_id = -1 means auto_decidet by server + * charakter->account_id is always matching its login table counter part, + * and its value will be written to charakter->account_id if everything succeeds. + * @param self: pointer to db + * @param charakter: pointer of mmo_charakter to save + * @return true if successful, false if something has failed + */ +static bool char_db_sql_create(CharDB* self, struct mmo_charakter* charakter) { + CharDB_SQL* db = (CharDB_SQL*)self; + Sql* sql_handle_char = db->charakters; + + // validate char id to assign + uint32 char_id; + char_id = charakter->char_id; + if (charakter->char_id != -1) + {// caller specifies it manually + char_id = charakter->char_id; + } + else + {// ask the database + char* data; + size_t len; + + if (SQL_SUCCESS != Sql_Query(sql_handle_char, "SELECT MAX(`char_id`)+1 FROM `%s`", db->char_db)) + { + Sql_ShowDebug(sql_handle_char); + return false; + } + if (SQL_SUCCESS != Sql_NextRow(sql_handle_char)) + { + Sql_ShowDebug(sql_handle_char); + Sql_FreeResult(sql_handle_char); + return false; + } + + Sql_GetData(sql_handle_char, 0, &data, &len); + char_id = (data != nullptr) ? atoi(data) : 0; + Sql_FreeResult(sql_handle_char); + char_id = max((uint32_t)START_CHAR_NUM, char_id); + } + + // zero value is prohibited + if(char_id == 0 ) + return false; + + // absolute maximum + if(char_id > END_ACCOUNT_NUM ) + return false; + + // insert the data into the database + charakter->char_id = char_id; + return mmo_char_auth_tosql(db, charakter, true); +} + +/** + * Delete an existing char entry and its regs. + * @param self: pointer to db + * @param char_id: id of charakter + * @return true if successful, false if something has failed + */ +static bool char_db_sql_remove(CharDB* self, const uint32 char_id) { + CharDB_SQL* db = (CharDB_SQL*)self; + Sql* sql_handle = db->charakters; + bool result = false; + + if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") + || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = %d", db->char_db, char_id) + ) + Sql_ShowDebug(sql_handle); + else + result = true; + + result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); + + return result; +} + +/** + * Update an existing charakter with the new data provided. + * @param self: pointer to db + * @param charakter: pointer of mmo_charakter to save + * @return true if successful, false if something has failed + */ +static bool char_db_sql_save(CharDB* self, const struct mmo_charakter* charakter) { + CharDB_SQL* db = (CharDB_SQL*)self; + return mmo_char_auth_tosql(db, charakter, false); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to mmo_char_auth_fromsql. + * @param self: pointer to db + * @param charakter: pointer of mmo_charakter to fill + * @param char_id: id of charakter + * @return true if successful, false if something has failed + */ +static bool char_db_sql_load_num(CharDB* self, struct mmo_charakter* charakter, const uint32 char_id) { + CharDB_SQL* db = (CharDB_SQL*)self; + return mmo_char_auth_fromsql(db, charakter, char_id); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Doesn't actually retrieve data yet: escapes and checks char_name, then transforms it to char_id for fetching. + * Filled data structure is done by delegation to char_db_sql_load_num. + * @param self: pointer to db + * @param charakter: pointer of mmo_charakter to fill + * @param char_name: name of charakter + * @return true if successful, false if something has failed + */ +static bool char_db_sql_load_str(CharDB* self, struct mmo_charakter* charakter, const char* char_name) { + CharDB_SQL* db = (CharDB_SQL*)self; + Sql* sql_handle = db->charakters; + char esc_char_name[2*NAME_LENGTH+1]; + uint32 char_id; + char* data; + + Sql_EscapeString(sql_handle, esc_char_name, char_name); + + // get the list of char IDs for this char_name + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id` FROM `%s` WHERE `name`= %s '%s'", + db->char_db, (db->case_sensitive ? "BINARY" : ""), esc_char_name) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + + if( Sql_NumRows(sql_handle) > 1 ) + {// serious problem - duplicit account + ShowError("char_db_sql_load_str: multiple charakters found when retrieving data for charakter '%s'!\n", char_name); + 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, nullptr); + char_id = atoi(data); + + return char_db_sql_load_num(self, charakter, char_id); +} + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static CharakterDBIterator* char_db_sql_iterator(CharDB* self) { + CharDB_SQL* db = (CharDB_SQL*)self; + CharakterDBIterator_SQL* iter = (CharakterDBIterator_SQL*)aCalloc(1, sizeof(CharakterDBIterator_SQL)); + + // set up the vtable + iter->vtable.destroy = &char_db_sql_iter_destroy; + iter->vtable.next = &char_db_sql_iter_next; + + // fill data + iter->db = db; + iter->last_char_id = -1; + + return &iter->vtable; +} + +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void char_db_sql_iter_destroy(CharakterDBIterator* self) { + CharakterDBIterator_SQL* iter = (CharakterDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next charakter in the database. + * @param self: pointer to db iterator + * @param charakter: pointer of mmo_charakter to fill + * @return true if next charakter found and filled, false if something has failed + */ +static bool char_db_sql_iter_next(CharakterDBIterator* self, struct mmo_charakter* charakter) { + CharakterDBIterator_SQL* iter = (CharakterDBIterator_SQL*)self; + CharDB_SQL* db = iter->db; + Sql* sql_handle = db->charakters; + char* data; + + // get next account ID + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `char_id` FROM `%s` WHERE `char_id` > '%d' ORDER BY `char_id` ASC LIMIT 1", + db->char_db, iter->last_char_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) && + SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) && + data != nullptr ) + {// get char data + uint32 char_id; + char_id = atoi(data); + if( mmo_char_auth_fromsql(db, charakter, char_id) ) + { + iter->last_char_id = char_id; + Sql_FreeResult(sql_handle); + return true; + } + } + Sql_FreeResult(sql_handle); + return false; +} + +/** + * Fetch a struct mmo_charakter from sql. + * @param db: pointer to db + * @param charakter: pointer of mmo_charakter to fill + * @param char_id: id of charakter to take data from + * @return true if successful, false if something has failed + */ +static bool mmo_char_auth_fromsql(CharDB_SQL* db, struct mmo_charakter* charakter, uint32 char_id) { + Sql* sql_handle = db->charakters; + char* data; + + // retrieve entry for the specified charakter + if( SQL_ERROR == Sql_Query(sql_handle, +#ifdef VIP_ENABLE + "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`pow`,`sta`,`wis`,`spl`,`con`,`crt`,`max_hp`,`hp`,`max_sp`,`sp`,`max_ap`,`ap`,`status_point`,`skill_point`,`trait_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,`hair_color`,`clothes_color`,`body`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`robe`,`last_map`,`last_x`,`last_y`,`last_instanceid`,`save_map`,`save_x`,`save_y`,`partner_id`,`online`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`moves`,`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`hotkey_rowshift2`,`clan_id`,`last_login`,`title_id`,`show_equip`,`inventory_slots`,`body_direction`,`disable_call` FROM `%s` WHERE `char_id` = %d", +#else + "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`pow`,`sta`,`wis`,`spl`,`con`,`crt`,`max_hp`,`hp`,`max_sp`,`sp`,`max_ap`,`ap`,`status_point`,`skill_point`,`trait_point`,`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`homun_id`,`elemental_id`,`hair`,`hair_color`,`clothes_color`,`body`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`,`robe`,`last_map`,`last_x`,`last_y`,`last_instanceid`,`save_map`,`save_x`,`save_y`,`partner_id`,`online`,`father`,`mother`,`child`,`fame`,`rename`,`delete_date`,`moves`,`unban_time`,`font`,`uniqueitem_counter`,`sex`,`hotkey_rowshift`,`hotkey_rowshift2`,`clan_id`,`last_login`,`title_id`,`show_equip`,`inventory_slots`,`body_direction`,`disable_call` FROM `%s` WHERE `char_id` = %d", +#endif + db->char_db, char_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, nullptr); charakter->char_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, nullptr); charakter->account_id = atoi(data); + Sql_GetData(sql_handle, 2, &data, nullptr); charakter->char_num = (uint8)atoi(data); + Sql_GetData(sql_handle, 3, &data, nullptr); safestrncpy(charakter->name, data, sizeof(charakter->name)); + + Sql_GetData(sql_handle, 4, &data, nullptr); charakter->class_ = (uint16)atoi(data); + Sql_GetData(sql_handle, 5, &data, nullptr); charakter->base_level = (uint16)atoi(data); + Sql_GetData(sql_handle, 6, &data, nullptr); charakter->job_level = (uint16)atoi(data); + Sql_GetData(sql_handle, 7, &data, nullptr); charakter->base_exp = strtoull(data, nullptr, 10); + Sql_GetData(sql_handle, 8, &data, nullptr); charakter->job_exp = strtoull(data, nullptr, 10); + Sql_GetData(sql_handle, 9, &data, nullptr); charakter->zeny = (uint32)atoi(data); + + Sql_GetData(sql_handle, 10, &data, nullptr); charakter->str = (uint16)atoi(data); + Sql_GetData(sql_handle, 11, &data, nullptr); charakter->agi = (uint16)atoi(data); + Sql_GetData(sql_handle, 12, &data, nullptr); charakter->vit = (uint16)atoi(data); + Sql_GetData(sql_handle, 13, &data, nullptr); charakter->int_ = (uint16)atoi(data); + Sql_GetData(sql_handle, 14, &data, nullptr); charakter->dex = (uint16)atoi(data); + Sql_GetData(sql_handle, 15, &data, nullptr); charakter->luk = (uint16)atoi(data); + + Sql_GetData(sql_handle, 16, &data, nullptr); charakter->pow = (uint16)atoi(data); + Sql_GetData(sql_handle, 17, &data, nullptr); charakter->sta = (uint16)atoi(data); + Sql_GetData(sql_handle, 18, &data, nullptr); charakter->wis = (uint16)atoi(data); + Sql_GetData(sql_handle, 19, &data, nullptr); charakter->spl = (uint16)atoi(data); + Sql_GetData(sql_handle, 20, &data, nullptr); charakter->con = (uint16)atoi(data); + Sql_GetData(sql_handle, 21, &data, nullptr); charakter->crt = (uint16)atoi(data); + + Sql_GetData(sql_handle, 22, &data, nullptr); charakter->max_hp = (uint32)atoi(data); + Sql_GetData(sql_handle, 23, &data, nullptr); charakter->hp = (uint32)atoi(data); + Sql_GetData(sql_handle, 24, &data, nullptr); charakter->max_sp = (uint32)atoi(data); + Sql_GetData(sql_handle, 25, &data, nullptr); charakter->sp = (uint32)atoi(data); + Sql_GetData(sql_handle, 26, &data, nullptr); charakter->max_ap = (uint32)atoi(data); + Sql_GetData(sql_handle, 27, &data, nullptr); charakter->ap = (uint32)atoi(data); + + Sql_GetData(sql_handle, 28, &data, nullptr); charakter->status_point = (uint32)atoi(data); + Sql_GetData(sql_handle, 29, &data, nullptr); charakter->skill_point = (uint32)atoi(data); + Sql_GetData(sql_handle, 30, &data, nullptr); charakter->trait_point = (uint32)atoi(data); + + Sql_GetData(sql_handle, 31, &data, nullptr); charakter->option = atoi(data); + Sql_GetData(sql_handle, 32, &data, nullptr); charakter->karma = (int8)atoi(data); + Sql_GetData(sql_handle, 33, &data, nullptr); charakter->manner = (int16)atoi(data); + + Sql_GetData(sql_handle, 34, &data, nullptr); charakter->party_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 35, &data, nullptr); charakter->guild_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 36, &data, nullptr); charakter->pet_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 37, &data, nullptr); charakter->homun_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 38, &data, nullptr); charakter->elemental_id = (uint32)atoi(data); + + Sql_GetData(sql_handle, 39, &data, nullptr); charakter->hair = (uint8)atoi(data); + Sql_GetData(sql_handle, 40, &data, nullptr); charakter->hair_color = (uint16)atoi(data); + Sql_GetData(sql_handle, 41, &data, nullptr); charakter->clothes_color = (uint16)atoi(data); + Sql_GetData(sql_handle, 42, &data, nullptr); charakter->body = (uint16)atoi(data); + + Sql_GetData(sql_handle, 43, &data, nullptr); charakter->weapon = (uint16)atoi(data); + Sql_GetData(sql_handle, 44, &data, nullptr); charakter->shield = (uint16)atoi(data); + Sql_GetData(sql_handle, 45, &data, nullptr); charakter->head_top = (uint16)atoi(data); + Sql_GetData(sql_handle, 46, &data, nullptr); charakter->head_mid = (uint16)atoi(data); + Sql_GetData(sql_handle, 47, &data, nullptr); charakter->head_bottom = (uint16)atoi(data); + Sql_GetData(sql_handle, 48, &data, nullptr); charakter->robe = (uint16)atoi(data); + + Sql_GetData(sql_handle, 49, &data, nullptr); safestrncpy(charakter->last_map, data, sizeof(charakter->last_map)); + Sql_GetData(sql_handle, 50, &data, nullptr); charakter->last_x = (uint16)atoi(data); + Sql_GetData(sql_handle, 51, &data, nullptr); charakter->last_y = (uint16)atoi(data); + Sql_GetData(sql_handle, 52, &data, nullptr); charakter->last_instanceid = (uint32)atoi(data); + + Sql_GetData(sql_handle, 53, &data, nullptr); safestrncpy(charakter->save_map, data, sizeof(charakter->save_map)); + Sql_GetData(sql_handle, 54, &data, nullptr); charakter->save_x = (uint16)atoi(data); + Sql_GetData(sql_handle, 55, &data, nullptr); charakter->save_y = (uint16)atoi(data); + + Sql_GetData(sql_handle, 56, &data, nullptr); charakter->partner_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 57, &data, nullptr); charakter->online = (uint8)atoi(data); + Sql_GetData(sql_handle, 58, &data, nullptr); charakter->father = (uint32)atoi(data); + Sql_GetData(sql_handle, 59, &data, nullptr); charakter->mother = (uint32)atoi(data); + Sql_GetData(sql_handle, 60, &data, nullptr); charakter->child = (uint32)atoi(data); + + Sql_GetData(sql_handle, 61, &data, nullptr); charakter->fame = (uint32)atoi(data); + Sql_GetData(sql_handle, 62, &data, nullptr); charakter->rename = (uint16)atoi(data); + Sql_GetData(sql_handle, 63, &data, nullptr); charakter->delete_date = (uint32)atoi(data); + Sql_GetData(sql_handle, 64, &data, nullptr); charakter->moves = (uint32)atoi(data); + Sql_GetData(sql_handle, 65, &data, nullptr); charakter->unban_time = (uint32)atoi(data); + Sql_GetData(sql_handle, 66, &data, nullptr); charakter->font = (int8)atoi(data); + Sql_GetData(sql_handle, 67, &data, nullptr); charakter->uniqueitem_counter = (uint32)atoi(data); + + Sql_GetData(sql_handle, 68, &data, nullptr); charakter->sex = data[0]; // assuming single char, 'M' or 'F' + + Sql_GetData(sql_handle, 69, &data, nullptr); charakter->hotkey_rowshift = (int8)atoi(data); + Sql_GetData(sql_handle, 70, &data, nullptr); charakter->hotkey_rowshift2 = (int8)atoi(data); + Sql_GetData(sql_handle, 71, &data, nullptr); charakter->clan_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 72, &data, nullptr); safestrncpy(charakter->last_login, data == nullptr ? "" : data, sizeof(charakter->last_login)); + + Sql_GetData(sql_handle, 73, &data, nullptr); charakter->title_id = (uint32)atoi(data); + Sql_GetData(sql_handle, 74, &data, nullptr); charakter->show_equip = (uint8)atoi(data); + Sql_GetData(sql_handle, 75, &data, nullptr); charakter->inventory_slots = (uint16)atoi(data); + Sql_GetData(sql_handle, 76, &data, nullptr); charakter->body_direction = (uint8)atoi(data); + Sql_GetData(sql_handle, 77, &data, nullptr); charakter->disable_call = (uint8)atoi(data); + +//ifdef VIP_ENABLE + +//endif + Sql_FreeResult(sql_handle); + + return true; +} + +/** + * Save a struct mmo_charakter in sql. + * @param db: pointer to db + * @param charakter: pointer of mmo_charakter to save + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* charakter, bool is_new) { + Sql* sql_handle = db->charakters; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // Try block for transaction + do { + if (SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION")) { + Sql_ShowDebug(sql_handle); + break; + } + + if (is_new) { + // Insert into character table + if (SQL_SUCCESS != SqlStmt_Prepare(stmt, + "INSERT INTO `%s` (" + "`char_id`, `account_id`, `char_num`, `name`, `class`, `sex`, " // Meta-Data + "`base_level`, `job_level`, `base_exp`, `job_exp`, `zeny`, " //Exp rates and money + "`str`, `agi`, `vit`, `int`, `dex`, `luk`, " //base stats + "`pow`, `sta`, `wis`, `spl`, `con`, `crt`, " //advanced stats + "`max_hp`, `hp`, `max_sp`, `sp`, `max_ap`, `ap`, " //hp,sp,ap + "`status_point`, `skill_point`, `trait_point`, " //level points + "`option`, `karma`, `manner`, `hair`, `hair_color`, `clothes_color`, " //optionals & cosmetics + //"`party_id`, `guild_id`, `pet_id`, `homun_id`, `elemental_id`, " // TODO check if needed while bot_char creation (guild is definately needed for bot culture features | for example prontera police, morrocian mafia, yunovian hussars) + "`body`, `weapon`, `shield`, `head_top`, `head_mid`, `head_bottom`, `robe`, " //equip + "`last_map`, `last_x`, `last_y`, `last_instanceid`, `save_map`, `save_x`, `save_y`," //location data + "`partner_id`, `online`, `father`, `mother`, `child`, `fame`, `unban_time`, `font`, `uniqueitem_counter`, " //relational & social data + "`last_login`, `title_id`, `show_equip`, `inventory_slots`, `body_direction`, `disable_call`) " //logging, flags and misc data + "VALUES (" + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?)", //67 Parameters total + db->char_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT32, (void*)&charakter->char_id, sizeof(charakter->char_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT32, (void*)&charakter->account_id, sizeof(charakter->account_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT8, (void*)&charakter->char_num, sizeof(charakter->char_num)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void*)charakter->name, strlen(charakter->name)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT16, (void*)&charakter->class_, sizeof(charakter->class_)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_STRING, (void*)&charakter->sex, sizeof(charakter->sex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_INT16, (void*)&charakter->base_level, sizeof(charakter->base_level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_INT16, (void*)&charakter->job_level, sizeof(charakter->job_level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_INT64, (void*)&charakter->base_exp, sizeof(charakter->base_exp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_INT64, (void*)&charakter->job_exp, sizeof(charakter->job_exp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_INT32, (void*)&charakter->zeny, sizeof(charakter->zeny)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_INT16, (void*)&charakter->str, sizeof(charakter->str)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_INT16, (void*)&charakter->agi, sizeof(charakter->agi)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_INT16, (void*)&charakter->vit, sizeof(charakter->vit)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_INT16, (void*)&charakter->int_, sizeof(charakter->int_)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_INT16, (void*)&charakter->dex, sizeof(charakter->dex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_INT16, (void*)&charakter->luk, sizeof(charakter->luk)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 17, SQLDT_INT16, (void*)&charakter->pow, sizeof(charakter->pow)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 18, SQLDT_INT16, (void*)&charakter->sta, sizeof(charakter->sta)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 19, SQLDT_INT16, (void*)&charakter->wis, sizeof(charakter->wis)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 20, SQLDT_INT16, (void*)&charakter->spl, sizeof(charakter->spl)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 21, SQLDT_INT16, (void*)&charakter->con, sizeof(charakter->con)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 22, SQLDT_INT16, (void*)&charakter->crt, sizeof(charakter->crt)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 23, SQLDT_INT32, (void*)&charakter->max_hp, sizeof(charakter->max_hp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 24, SQLDT_INT32, (void*)&charakter->hp, sizeof(charakter->hp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 25, SQLDT_INT32, (void*)&charakter->max_sp, sizeof(charakter->max_sp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 26, SQLDT_INT32, (void*)&charakter->sp, sizeof(charakter->sp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 27, SQLDT_INT32, (void*)&charakter->max_ap, sizeof(charakter->max_ap)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 28, SQLDT_INT32, (void*)&charakter->ap, sizeof(charakter->ap)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 29, SQLDT_INT32, (void*)&charakter->status_point, sizeof(charakter->status_point)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 30, SQLDT_INT32, (void*)&charakter->skill_point, sizeof(charakter->skill_point)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 31, SQLDT_INT32, (void*)&charakter->trait_point, sizeof(charakter->trait_point)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 32, SQLDT_INT32, (void*)&charakter->option, sizeof(charakter->option)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 33, SQLDT_INT8, (void*)&charakter->karma, sizeof(charakter->karma)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 34, SQLDT_INT16, (void*)&charakter->manner, sizeof(charakter->manner)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 35, SQLDT_INT8, (void*)&charakter->hair, sizeof(charakter->hair)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 36, SQLDT_INT16, (void*)&charakter->hair_color, sizeof(charakter->hair_color)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 37, SQLDT_INT16, (void*)&charakter->clothes_color, sizeof(charakter->clothes_color)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 38, SQLDT_INT16, (void*)&charakter->body, sizeof(charakter->body)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 39, SQLDT_INT16, (void*)&charakter->weapon, sizeof(charakter->weapon)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 40, SQLDT_INT16, (void*)&charakter->shield, sizeof(charakter->shield)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 41, SQLDT_INT16, (void*)&charakter->head_top, sizeof(charakter->head_top)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 42, SQLDT_INT16, (void*)&charakter->head_mid, sizeof(charakter->head_mid)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 43, SQLDT_INT16, (void*)&charakter->head_bottom, sizeof(charakter->head_bottom)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 44, SQLDT_INT16, (void*)&charakter->robe, sizeof(charakter->robe)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 45, SQLDT_STRING, (void*)charakter->last_map, strlen(charakter->last_map)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 46, SQLDT_INT16, (void*)&charakter->last_x, sizeof(charakter->last_x)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 47, SQLDT_INT16, (void*)&charakter->last_y, sizeof(charakter->last_y)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 48, SQLDT_INT32, (void*)&charakter->last_instanceid, sizeof(charakter->last_instanceid)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 49, SQLDT_STRING, (void*)charakter->save_map, strlen(charakter->save_map)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 50, SQLDT_INT16, (void*)&charakter->save_x, sizeof(charakter->save_x)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 51, SQLDT_INT16, (void*)&charakter->save_y, sizeof(charakter->save_y)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 52, SQLDT_INT32, (void*)&charakter->partner_id, sizeof(charakter->partner_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 53, SQLDT_INT8, (void*)&charakter->online, sizeof(charakter->online)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 54, SQLDT_INT32, (void*)&charakter->father, sizeof(charakter->father)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 55, SQLDT_INT32, (void*)&charakter->mother, sizeof(charakter->mother)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 56, SQLDT_INT32, (void*)&charakter->child, sizeof(charakter->child)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 57, SQLDT_INT32, (void*)&charakter->fame, sizeof(charakter->fame)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 58, SQLDT_INT32, (void*)&charakter->unban_time, sizeof(charakter->unban_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 59, SQLDT_INT8, (void*)&charakter->font, sizeof(charakter->font)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 60, SQLDT_INT32, (void*)&charakter->uniqueitem_counter, sizeof(charakter->uniqueitem_counter)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 61, charakter->last_login[0] ? SQLDT_STRING : SQLDT_NULL, (void*)&charakter->last_login, strlen(charakter->last_login)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 62, SQLDT_INT32, (void*)&charakter->title_id, sizeof(charakter->title_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 63, SQLDT_INT8, (void*)&charakter->show_equip, sizeof(charakter->show_equip)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 64, SQLDT_INT16, (void*)&charakter->inventory_slots, sizeof(charakter->inventory_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 65, SQLDT_INT8, (void*)&charakter->body_direction, sizeof(charakter->body_direction)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 66, SQLDT_INT8, (void*)&charakter->disable_call, sizeof(charakter->disable_call)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else { + // Update character record + if (SQL_SUCCESS != SqlStmt_Prepare(stmt, + "UPDATE `%s` SET `name`=?, `class`=?, `base_level`=?, `job_level`=?, `base_exp`=?, `job_exp`=?, `zeny`=?, " + "`str`=?, `agi`=?, `vit`=?, `int`=?, `dex`=?, `luk`=?, `hair`=?, `hair_color`=?, `max_hp`=?, `hp`=?, " + "`max_sp`=?, `sp`=?, `last_map`=?, `last_x`=?, `last_y`=?, `sex`=?, `last_login`=?, `save_map`=?, `save_x`=?, " + "`save_y`=?, `partner_id`=?, `online`=?, `fame`=?, `delete_date`=?, `moves`=?, `unban_time`=?, `inventory_slots`=?, " + "`font`=?, `clan_id`=?, `title_id`=?, `show_equip`=?, `body_direction`=?, `disable_call`=? WHERE `char_id` = ?", + db->char_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)charakter->name, strlen(charakter->name)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT16, (void*)&charakter->class_, sizeof(charakter->class_)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT16, (void*)&charakter->base_level, sizeof(charakter->base_level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_INT16, (void*)&charakter->job_level, sizeof(charakter->job_level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT64, (void*)&charakter->base_exp, sizeof(charakter->base_exp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_INT64, (void*)&charakter->job_exp, sizeof(charakter->job_exp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_INT, (void*)&charakter->zeny, sizeof(charakter->zeny)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_INT16, (void*)&charakter->str, sizeof(charakter->str)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_INT16, (void*)&charakter->agi, sizeof(charakter->agi)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_INT16, (void*)&charakter->vit, sizeof(charakter->vit)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_INT16, (void*)&charakter->int_, sizeof(charakter->int_)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_INT16, (void*)&charakter->dex, sizeof(charakter->dex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_INT16, (void*)&charakter->luk, sizeof(charakter->luk)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_INT8, (void*)&charakter->hair, sizeof(charakter->hair)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_INT16, (void*)&charakter->hair_color, sizeof(charakter->hair_color)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_INT, (void*)&charakter->max_hp, sizeof(charakter->max_hp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_INT, (void*)&charakter->hp, sizeof(charakter->hp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 17, SQLDT_INT, (void*)&charakter->max_sp, sizeof(charakter->max_sp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 18, SQLDT_INT, (void*)&charakter->sp, sizeof(charakter->sp)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 19, SQLDT_STRING, (void*)charakter->last_map, strlen(charakter->last_map)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 20, SQLDT_INT16, (void*)&charakter->last_x, sizeof(charakter->last_x)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 21, SQLDT_INT16, (void*)&charakter->last_y, sizeof(charakter->last_y)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 22, SQLDT_STRING, (void*)&charakter->sex, sizeof(charakter->sex)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 23, charakter->last_login[0] ? SQLDT_STRING : SQLDT_NULL, (void*)&charakter->last_login, strlen(charakter->last_login)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 24, SQLDT_STRING, (void*)charakter->save_map, strlen(charakter->save_map)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 25, SQLDT_INT16, (void*)&charakter->save_x, sizeof(charakter->save_x)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 26, SQLDT_INT16, (void*)&charakter->save_y, sizeof(charakter->save_y)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 27, SQLDT_INT, (void*)&charakter->partner_id, sizeof(charakter->partner_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 28, SQLDT_INT8, (void*)&charakter->online, sizeof(charakter->online)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 29, SQLDT_INT, (void*)&charakter->fame, sizeof(charakter->fame)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 30, SQLDT_INT, (void*)&charakter->delete_date, sizeof(charakter->delete_date)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 31, SQLDT_INT, (void*)&charakter->moves, sizeof(charakter->moves)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 32, SQLDT_INT, (void*)&charakter->unban_time, sizeof(charakter->unban_time)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 33, SQLDT_INT16, (void*)&charakter->inventory_slots, sizeof(charakter->inventory_slots)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 34, SQLDT_INT8, (void*)&charakter->font, sizeof(charakter->font)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 35, SQLDT_INT, (void*)&charakter->clan_id, sizeof(charakter->clan_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 36, SQLDT_INT, (void*)&charakter->title_id, sizeof(charakter->title_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 37, SQLDT_INT8, (void*)&charakter->show_equip, sizeof(charakter->show_equip)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 38, SQLDT_INT8, (void*)&charakter->body_direction, sizeof(charakter->body_direction)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 39, SQLDT_INT8, (void*)&charakter->disable_call, sizeof(charakter->disable_call)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 40, SQLDT_INT, (void*)&charakter->char_id, sizeof(charakter->char_id)) + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + + // If we got this far, everything was successful + result = true; + + } while (0); + + // Commit or rollback transaction + result &= (SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK")); + SqlStmt_Free(stmt); + + return result; +} + +// Function to convert job_id string to job number +int convert_jobid_to_number(const char* job_id) { + // Create a map for the job ID strings to the corresponding e_job enum values + static const std::unordered_map job_map = { + {"JOB_NOVICE", JOB_NOVICE}, + {"JOB_SWORDMAN", JOB_SWORDMAN}, + {"JOB_MAGE", JOB_MAGE}, + {"JOB_ARCHER", JOB_ARCHER}, + {"JOB_ACOLYTE", JOB_ACOLYTE}, + {"JOB_MERCHANT", JOB_MERCHANT}, + {"JOB_THIEF", JOB_THIEF}, + {"JOB_KNIGHT", JOB_KNIGHT}, + {"JOB_PRIEST", JOB_PRIEST}, + {"JOB_WIZARD", JOB_WIZARD}, + {"JOB_BLACKSMITH", JOB_BLACKSMITH}, + {"JOB_HUNTER", JOB_HUNTER}, + {"JOB_ASSASSIN", JOB_ASSASSIN}, + {"JOB_KNIGHT2", JOB_KNIGHT2}, + {"JOB_CRUSADER", JOB_CRUSADER}, + {"JOB_MONK", JOB_MONK}, + {"JOB_SAGE", JOB_SAGE}, + {"JOB_ROGUE", JOB_ROGUE}, + {"JOB_ALCHEMIST", JOB_ALCHEMIST}, + {"JOB_BARD", JOB_BARD}, + {"JOB_DANCER", JOB_DANCER}, + {"JOB_CRUSADER2", JOB_CRUSADER2}, + {"JOB_WEDDING", JOB_WEDDING}, + {"JOB_SUPER_NOVICE", JOB_SUPER_NOVICE}, + {"JOB_GUNSLINGER", JOB_GUNSLINGER}, + {"JOB_NINJA", JOB_NINJA}, + {"JOB_XMAS", JOB_XMAS}, + {"JOB_SUMMER", JOB_SUMMER}, + {"JOB_HANBOK", JOB_HANBOK}, + {"JOB_OKTOBERFEST", JOB_OKTOBERFEST}, + {"JOB_SUMMER2", JOB_SUMMER2}, + {"JOB_MAX_BASIC", JOB_MAX_BASIC}, + + {"JOB_NOVICE_HIGH", JOB_NOVICE_HIGH}, + {"JOB_SWORDMAN_HIGH", JOB_SWORDMAN_HIGH}, + {"JOB_MAGE_HIGH", JOB_MAGE_HIGH}, + {"JOB_ARCHER_HIGH", JOB_ARCHER_HIGH}, + {"JOB_ACOLYTE_HIGH", JOB_ACOLYTE_HIGH}, + {"JOB_MERCHANT_HIGH", JOB_MERCHANT_HIGH}, + {"JOB_THIEF_HIGH", JOB_THIEF_HIGH}, + {"JOB_LORD_KNIGHT", JOB_LORD_KNIGHT}, + {"JOB_HIGH_PRIEST", JOB_HIGH_PRIEST}, + {"JOB_HIGH_WIZARD", JOB_HIGH_WIZARD}, + {"JOB_WHITESMITH", JOB_WHITESMITH}, + {"JOB_SNIPER", JOB_SNIPER}, + {"JOB_ASSASSIN_CROSS", JOB_ASSASSIN_CROSS}, + {"JOB_LORD_KNIGHT2", JOB_LORD_KNIGHT2}, + {"JOB_PALADIN", JOB_PALADIN}, + {"JOB_CHAMPION", JOB_CHAMPION}, + {"JOB_PROFESSOR", JOB_PROFESSOR}, + {"JOB_STALKER", JOB_STALKER}, + {"JOB_CREATOR", JOB_CREATOR}, + {"JOB_CLOWN", JOB_CLOWN}, + {"JOB_GYPSY", JOB_GYPSY}, + {"JOB_PALADIN2", JOB_PALADIN2}, + + {"JOB_BABY", JOB_BABY}, + {"JOB_BABY_SWORDMAN", JOB_BABY_SWORDMAN}, + {"JOB_BABY_MAGE", JOB_BABY_MAGE}, + {"JOB_BABY_ARCHER", JOB_BABY_ARCHER}, + {"JOB_BABY_ACOLYTE", JOB_BABY_ACOLYTE}, + {"JOB_BABY_MERCHANT", JOB_BABY_MERCHANT}, + {"JOB_BABY_THIEF", JOB_BABY_THIEF}, + {"JOB_BABY_KNIGHT", JOB_BABY_KNIGHT}, + {"JOB_BABY_PRIEST", JOB_BABY_PRIEST}, + {"JOB_BABY_WIZARD", JOB_BABY_WIZARD}, + {"JOB_BABY_BLACKSMITH", JOB_BABY_BLACKSMITH}, + {"JOB_BABY_HUNTER", JOB_BABY_HUNTER}, + {"JOB_BABY_ASSASSIN", JOB_BABY_ASSASSIN}, + {"JOB_BABY_KNIGHT2", JOB_BABY_KNIGHT2}, + {"JOB_BABY_CRUSADER", JOB_BABY_CRUSADER}, + {"JOB_BABY_MONK", JOB_BABY_MONK}, + {"JOB_BABY_SAGE", JOB_BABY_SAGE}, + {"JOB_BABY_ROGUE", JOB_BABY_ROGUE}, + {"JOB_BABY_ALCHEMIST", JOB_BABY_ALCHEMIST}, + {"JOB_BABY_BARD", JOB_BABY_BARD}, + {"JOB_BABY_DANCER", JOB_BABY_DANCER}, + {"JOB_BABY_CRUSADER2", JOB_BABY_CRUSADER2}, + {"JOB_SUPER_BABY", JOB_SUPER_BABY}, + + {"JOB_TAEKWON", JOB_TAEKWON}, + {"JOB_STAR_GLADIATOR", JOB_STAR_GLADIATOR}, + {"JOB_STAR_GLADIATOR2", JOB_STAR_GLADIATOR2}, + {"JOB_SOUL_LINKER", JOB_SOUL_LINKER}, + + {"JOB_GANGSI", JOB_GANGSI}, + {"JOB_DEATH_KNIGHT", JOB_DEATH_KNIGHT}, + {"JOB_DARK_COLLECTOR", JOB_DARK_COLLECTOR}, + + {"JOB_RUNE_KNIGHT", JOB_RUNE_KNIGHT}, + {"JOB_WARLOCK", JOB_WARLOCK}, + {"JOB_RANGER", JOB_RANGER}, + {"JOB_ARCH_BISHOP", JOB_ARCH_BISHOP}, + {"JOB_MECHANIC", JOB_MECHANIC}, + {"JOB_GUILLOTINE_CROSS", JOB_GUILLOTINE_CROSS}, + + {"JOB_RUNE_KNIGHT_T", JOB_RUNE_KNIGHT_T}, + {"JOB_WARLOCK_T", JOB_WARLOCK_T}, + {"JOB_RANGER_T", JOB_RANGER_T}, + {"JOB_ARCH_BISHOP_T", JOB_ARCH_BISHOP_T}, + {"JOB_MECHANIC_T", JOB_MECHANIC_T }, + { "JOB_GUILLOTINE_CROSS_T", JOB_GUILLOTINE_CROSS_T }, + + { "JOB_ROYAL_GUARD", JOB_ROYAL_GUARD }, + { "JOB_SORCERER", JOB_SORCERER }, + { "JOB_MINSTREL", JOB_MINSTREL }, + { "JOB_WANDERER", JOB_WANDERER }, + { "JOB_SURA", JOB_SURA }, + { "JOB_GENETIC", JOB_GENETIC }, + { "JOB_SHADOW_CHASER", JOB_SHADOW_CHASER }, + + { "JOB_ROYAL_GUARD_T", JOB_ROYAL_GUARD_T }, + { "JOB_SORCERER_T", JOB_SORCERER_T }, + { "JOB_MINSTREL_T", JOB_MINSTREL_T }, + { "JOB_WANDERER_T", JOB_WANDERER_T }, + { "JOB_SURA_T", JOB_SURA_T }, + { "JOB_GENETIC_T", JOB_GENETIC_T }, + { "JOB_SHADOW_CHASER_T", JOB_SHADOW_CHASER_T }, + + { "JOB_RUNE_KNIGHT2", JOB_RUNE_KNIGHT2 }, + { "JOB_RUNE_KNIGHT_T2", JOB_RUNE_KNIGHT_T2 }, + { "JOB_ROYAL_GUARD2", JOB_ROYAL_GUARD2 }, + { "JOB_ROYAL_GUARD_T2", JOB_ROYAL_GUARD_T2 }, + { "JOB_RANGER2", JOB_RANGER2 }, + { "JOB_RANGER_T2", JOB_RANGER_T2 }, + { "JOB_MECHANIC2", JOB_MECHANIC2 }, + { "JOB_MECHANIC_T2", JOB_MECHANIC_T2 }, + + { "JOB_BABY_RUNE_KNIGHT", JOB_BABY_RUNE_KNIGHT }, + { "JOB_BABY_WARLOCK", JOB_BABY_WARLOCK }, + { "JOB_BABY_RANGER", JOB_BABY_RANGER }, + { "JOB_BABY_ARCH_BISHOP", JOB_BABY_ARCH_BISHOP }, + { "JOB_BABY_MECHANIC", JOB_BABY_MECHANIC }, + { "JOB_BABY_GUILLOTINE_CROSS", JOB_BABY_GUILLOTINE_CROSS }, + { "JOB_BABY_ROYAL_GUARD", JOB_BABY_ROYAL_GUARD }, + { "JOB_BABY_SORCERER", JOB_BABY_SORCERER }, + { "JOB_BABY_MINSTREL", JOB_BABY_MINSTREL }, + { "JOB_BABY_WANDERER", JOB_BABY_WANDERER }, + { "JOB_BABY_SURA", JOB_BABY_SURA }, + { "JOB_BABY_GENETIC", JOB_BABY_GENETIC }, + { "JOB_BABY_SHADOW_CHASER", JOB_BABY_SHADOW_CHASER }, + + { "JOB_BABY_RUNE_KNIGHT2", JOB_BABY_RUNE_KNIGHT2 }, + { "JOB_BABY_ROYAL_GUARD2", JOB_BABY_ROYAL_GUARD2 }, + { "JOB_BABY_RANGER2", JOB_BABY_RANGER2 }, + { "JOB_BABY_MECHANIC2", JOB_BABY_MECHANIC2 }, + + { "JOB_SUPER_NOVICE_E", JOB_SUPER_NOVICE_E }, + { "JOB_SUPER_BABY_E", JOB_SUPER_BABY_E }, + + { "JOB_KAGEROU", JOB_KAGEROU }, + { "JOB_OBORO", JOB_OBORO }, + + { "JOB_REBELLION", JOB_REBELLION }, + + { "JOB_SUMMONER", JOB_SUMMONER }, + + { "JOB_BABY_SUMMONER", JOB_BABY_SUMMONER }, + + { "JOB_BABY_NINJA", JOB_BABY_NINJA }, + { "JOB_BABY_KAGEROU", JOB_BABY_KAGEROU }, + { "JOB_BABY_OBORO", JOB_BABY_OBORO }, + { "JOB_BABY_TAEKWON", JOB_BABY_TAEKWON }, + { "JOB_BABY_STAR_GLADIATOR", JOB_BABY_STAR_GLADIATOR }, + { "JOB_BABY_SOUL_LINKER", JOB_BABY_SOUL_LINKER }, + { "JOB_BABY_GUNSLINGER", JOB_BABY_GUNSLINGER }, + { "JOB_BABY_REBELLION", JOB_BABY_REBELLION }, + + { "JOB_BABY_STAR_GLADIATOR2", JOB_BABY_STAR_GLADIATOR2 }, + + { "JOB_STAR_EMPEROR", JOB_STAR_EMPEROR }, + { "JOB_SOUL_REAPER", JOB_SOUL_REAPER }, + { "JOB_BABY_STAR_EMPEROR", JOB_BABY_STAR_EMPEROR }, + { "JOB_BABY_SOUL_REAPER", JOB_BABY_SOUL_REAPER }, + { "JOB_STAR_EMPEROR2", JOB_STAR_EMPEROR2 }, + { "JOB_BABY_STAR_EMPEROR2", JOB_BABY_STAR_EMPEROR2 }, + + { "JOB_DRAGON_KNIGHT", JOB_DRAGON_KNIGHT }, + { "JOB_MEISTER", JOB_MEISTER }, + { "JOB_SHADOW_CROSS", JOB_SHADOW_CROSS }, + { "JOB_ARCH_MAGE", JOB_ARCH_MAGE }, + { "JOB_CARDINAL", JOB_CARDINAL }, + { "JOB_WINDHAWK", JOB_WINDHAWK }, + { "JOB_IMPERIAL_GUARD", JOB_IMPERIAL_GUARD }, + { "JOB_BIOLO", JOB_BIOLO }, + { "JOB_ABYSS_CHASER", JOB_ABYSS_CHASER }, + { "JOB_ELEMENTAL_MASTER", JOB_ELEMENTAL_MASTER }, + { "JOB_INQUISITOR", JOB_INQUISITOR }, + { "JOB_TROUBADOUR", JOB_TROUBADOUR }, + { "JOB_TROUVERE", JOB_TROUVERE }, + + { "JOB_WINDHAWK2", JOB_WINDHAWK2 }, + { "JOB_MEISTER2", JOB_MEISTER2 }, + { "JOB_DRAGON_KNIGHT2", JOB_DRAGON_KNIGHT2 }, + { "JOB_IMPERIAL_GUARD2", JOB_IMPERIAL_GUARD2 }, + + { "JOB_SKY_EMPEROR", JOB_SKY_EMPEROR }, + { "JOB_SOUL_ASCETIC", JOB_SOUL_ASCETIC }, + { "JOB_SHINKIRO", JOB_SHINKIRO }, + { "JOB_SHIRANUI", JOB_SHIRANUI }, + { "JOB_NIGHT_WATCH", JOB_NIGHT_WATCH }, + { "JOB_HYPER_NOVICE", JOB_HYPER_NOVICE }, + { "JOB_SPIRIT_HANDLER", JOB_SPIRIT_HANDLER }, + + { "JOB_SKY_EMPEROR2", JOB_SKY_EMPEROR2 }, + + // Add other job mappings here as needed + }; + + // Look up the job_id in the map + std::string job_str = job_id; + // Convert to upper case + for (int index = 0; index < job_str.size(); index++) { + job_str.at(index) = toupper(job_str.at(index)); + } + auto it = job_map.find(job_str); + if (it != job_map.end()) { + return it->second; // Return the corresponding enum value + } + else { + ShowError("Unknown job ID: %s\n", job_id); + return -1; // Return an error value if the job ID is unknown + } +} + +/* +* Weapon Types +*/ +enum weapon_type : uint8 { + 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 + MAX_WEAPON_TYPE_ALL, + W_SHIELD = MAX_WEAPON_TYPE, +}; + +#define WEAPON_TYPE_ALL ((1< weapon_map = { + {"W_FIST", W_FIST}, //Bare hands + {"W_DAGGER", W_DAGGER}, //1 + {"W_1HSWORD", W_1HSWORD}, //2 + {"W_2HSWORD", W_2HSWORD}, //3 + {"W_1HSPEAR", W_1HSPEAR}, //4 + {"W_2HSPEAR", W_2HSPEAR}, //5 + {"W_1HAXE", W_1HAXE}, //6 + {"W_2HAXE", W_2HAXE}, //7 + {"W_MACE", W_MACE}, //8 + {"W_2HMACE", W_2HMACE}, //9 (unused) + {"W_STAFF", W_STAFF}, //10 + {"W_BOW", W_BOW}, //11 + {"W_KNUCKLE", W_KNUCKLE}, //12 + {"W_MUSICAL", W_MUSICAL}, //13 + {"W_WHIP", W_WHIP}, //14 + {"W_BOOK", W_BOOK}, //15 + {"W_KATAR", W_KATAR}, //16 + {"W_REVOLVER", W_REVOLVER}, //17 + {"W_RIFLE", W_RIFLE}, //18 + {"W_GATLING", W_GATLING}, //19 + {"W_SHOTGUN", W_SHOTGUN}, //20 + {"W_GRENADE", W_GRENADE}, //21 + {"W_HUUMA", W_HUUMA}, //22 + {"W_2HSTAFF", W_2HSTAFF}, //23 + {"MAX_WEAPON_TYPE", MAX_WEAPON_TYPE}, + // dual-wield constants + {"W_DOUBLE_DD", W_DOUBLE_DD}, // 2 daggers + {"W_DOUBLE_SS", W_DOUBLE_SS}, // 2 swords + {"W_DOUBLE_AA", W_DOUBLE_AA}, // 2 axes + {"W_DOUBLE_DS", W_DOUBLE_DS}, // dagger + sword + {"W_DOUBLE_DA", W_DOUBLE_DA}, // dagger + axe + {"W_DOUBLE_SA", W_DOUBLE_SA}, // sword + axe + {"MAX_WEAPON_TYPE_ALL", MAX_WEAPON_TYPE_ALL}, + {"W_SHIELD", W_SHIELD}, + // Add other job mappings here as needed + }; + + // Look up the job_id in the map + std::string weapon_str = weapon_id; + // Convert to upper case + for (int index = 0; index < weapon_str.size(); index++) { + weapon_str.at(index) = toupper(weapon_str.at(index)); + } + auto it = weapon_map.find(weapon_str); + if (it != weapon_map.end()) { + return it->second; // Return the corresponding enum value + } + else { + ShowError("Unknown weapon ID: %s\n", weapon_id); + return -1; // Return an error value if the weapon ID is unknown + } +} diff --git a/src/bot/Brokk/charakters.hpp b/src/bot/Brokk/charakters.hpp new file mode 100644 index 0000000..03bdd8d --- /dev/null +++ b/src/bot/Brokk/charakters.hpp @@ -0,0 +1,216 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef CHARAKTERS_HPP +#define CHARAKTERS_HPP + +#include +#include // ACCOUNT_REG2_NUM, WEB_AUTH_TOKEN_LENGTH +#include + +typedef struct CharDB CharDB; +typedef struct CharakterDBIterator CharakterDBIterator; + + +// standard engines +CharDB* char_db_sql(void); + +struct mmo_charakter { + uint32 char_id; // Primary key + uint32 account_id; // ID of corresponding login account + uint8 char_num; // Slot number of the character + char name[NAME_LENGTH]; // Name of the character, limited to 30 chars + + uint16 class_; // Class ID of the character + uint16 base_level; // Base level of the character + uint16 job_level; // Job level of the character + + uint64 base_exp; // Base experience points + uint64 job_exp; // Job experience points + uint32 zeny; // Amount of zeny + + uint16 str; // Strength stat + uint16 agi; // Agility stat + uint16 vit; // Vitality stat + uint16 int_; // Intelligence stat + uint16 dex; // Dexterity stat + uint16 luk; // Luck stat + + // Additional stats + uint16 pow; // Power stat + uint16 sta; // Stamina stat + uint16 wis; // Wisdom stat + uint16 spl; // Spirit stat + uint16 con; // Concentration stat + uint16 crt; // Critical stat + + uint32 max_hp; // Maximum HP + uint32 hp; // Current HP + uint32 max_sp; // Maximum SP + uint32 sp; // Current SP + uint32 max_ap; // Maximum AP (if applicable) + uint32 ap; // Current AP (if applicable) + + uint32 status_point; // Available status points + uint32 skill_point; // Available skill points + uint32 trait_point; // Available trait points + + int32 option; // Character options + int8 karma; // Karma points (signed, -128 to 127) + int16 manner; // Manner points (could be negative, small int) + + uint32 party_id; // ID of the party the character is in + uint32 guild_id; // ID of the guild the character is in + uint32 pet_id; // ID of the pet (if any) + uint32 homun_id; // ID of the homunculus (if any) + uint32 elemental_id; // ID of the elemental (if any) + + uint8 hair; // Hair style + uint16 hair_color; // Hair color + uint16 clothes_color; // Clothes color + uint16 body; // Body ID + uint16 weapon; // Weapon ID + uint16 shield; // Shield ID + uint16 head_top; // Head top item + uint16 head_mid; // Head middle item + uint16 head_bottom; // Head bottom item + uint16 robe; // Robe item + + char last_map[MAP_NAME_LENGTH]; // Last known map the character was on + uint16 last_x; // Last X position on the map + uint16 last_y; // Last Y position on the map + uint32 last_instanceid; // ID of the last instance the character was in + + char save_map[MAP_NAME_LENGTH]; // Save map (respawn location) + uint16 save_x; // Save X position + uint16 save_y; // Save Y position + + uint32 partner_id; // ID of the partner (e.g., for marriage) + uint8 online; // Is the character online or not? + uint32 father; // Father character ID + uint32 mother; // Mother character ID + uint32 child; // Child character ID + + uint32 fame; // Fame points + uint16 rename; // Rename status + uint32 delete_date; // Scheduled deletion time (timestamp) + uint32 moves; // Character moves (could represent migration) + uint32 unban_time; // Time until unbanned (timestamp) + int8 font; // Font used by the character + uint32 uniqueitem_counter; // Counter for unique items + + char sex; // 'M' or 'F' for gender + int8 hotkey_rowshift; // Hotkey row shift setting + int8 hotkey_rowshift2; // Additional hotkey row shift setting + uint32 clan_id; // Clan ID + char last_login[24]; // date+time of last successful login + uint32 title_id; // Character title ID + uint8 show_equip; // Show equipment flag + uint16 inventory_slots; // Available inventory slots + uint8 body_direction; // Direction the body is facing + uint8 disable_call; // Disable call flag (e.g., for party/guild recall) + + // Add other custom fields here if needed for future expansions +}; + + +struct CharakterDBIterator { + /// Destroys this iterator, releasing all allocated memory (including itself). + /// + /// @param self Iterator + void (*destroy)(CharakterDBIterator* self); + + /// Fetches the next charakter in the database. + /// Fills charakter with the charakter data. + /// @param self Iterator + /// @param character data + /// @return true if successful + bool (*next)(CharakterDBIterator* self, struct mmo_charakter* charakter); +}; + + +struct CharDB { + /// Initializes this database, making it ready for use. + /// Call this after setting the properties. + /// + /// @param self Database + /// @return true if successful + bool (*init)(CharDB* self); + + /// Destroys this database, releasing all allocated memory (including itself). + /// + /// @param self Database + void (*destroy)(CharDB* self); + + /// Gets a property from this database. + /// These read-only properties must be implemented: + /// + /// @param self Database + /// @param key Property name + /// @param buf Buffer for the value + /// @param buflen Buffer length + /// @return true if successful + bool (*get_property)(CharDB* 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)(CharDB* self, const char* key, const char* value); + + /// Creates a new charakter in this database. + /// + /// @param self Database + /// @param charakter Charakter data + /// @return true if successful + bool (*create)(CharDB* self, struct mmo_charakter* charakter); + + /// Removes an charakter from this database. + /// + /// @param self Database + /// @param char_id Charakter id + /// @return true if successful + bool (*remove)(CharDB* self, const uint32 char_id); + + /// Modifies the data of an existing charakter. + /// Uses charakter->char_id to identify the charakter. + /// + /// @param self Database + /// @param charakter Char data + /// @return true if successful + bool (*save)(CharDB* self, const struct mmo_charakter* charakter); + + /// Finds an charakter with char_id and copies it to charakter. + /// + /// @param self Database + /// @param charakter Pointer that receives the char data + /// @param char_id Target charakter id + /// @return true if successful + bool (*load_num)(CharDB* self, struct mmo_charakter* charakter, const uint32 char_id); + + /// Finds an charakter with charakter_name and copies it to charakter. + /// + /// @param self Database + /// @param charakter Pointer that receives the charakter data + /// @param char_name Target Charakter Name + /// @return true if successful + bool (*load_str)(CharDB* self, struct mmo_charakter* charakter, const char* id); + + /// Returns a new forward iterator. + /// + /// @param self Database + /// @return Iterator + CharakterDBIterator* (*iterator)(CharDB* self); +}; + +//void mmo_send_global_accreg(BotAccountDB* self, int fd, uint32 account_id, uint32 char_id); +//void mmo_save_global_accreg(BotAccountDB* self, int fd, uint32 account_id, uint32 char_id); + +int convert_jobid_to_number(const char* job_id); +int convert_weaponid_to_number(const char* weapon_id); + +#endif /* CHARAKTERS_HPP */ diff --git a/src/bot/Brokk/config_helpers.cpp b/src/bot/Brokk/config_helpers.cpp new file mode 100644 index 0000000..419eb9e --- /dev/null +++ b/src/bot/Brokk/config_helpers.cpp @@ -0,0 +1,825 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + + +#include "bot_account.hpp" + +#include //min / max +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "brokk.hpp" // brokk_config + +/// global defines + +/// internal structure +typedef struct BotAccountDB_SQL { + BotAccountDB vtable; // public interface + Sql* bot_accounts; // SQL handle bot accounts storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = "ragnarok"; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char bot_account_db[32]; + char login_account_db[32]; + char global_acc_reg_num_table[32]; + char global_acc_reg_str_table[32]; + +} BotAccountDB_SQL; + +/// internal structure +typedef struct BotAccountDBIterator_SQL { + BotAccountDBIterator vtable; // public interface + BotAccountDB_SQL* db; + int last_bot_account_id; +} BotAccountDBIterator_SQL; + +/// internal functions +static bool bot_account_db_sql_init(BotAccountDB* self); +static void bot_account_db_sql_destroy(BotAccountDB* self); +static bool bot_account_db_sql_get_property(BotAccountDB* self, const char* key, char* buf, size_t buflen); +static bool bot_account_db_sql_set_property(BotAccountDB* self, const char* option, const char* value); +static bool bot_account_db_sql_create(BotAccountDB* self, struct mmo_bot_account* bot_acc); +static bool bot_account_db_sql_remove(BotAccountDB* self, const uint32 account_id); +static bool bot_account_db_sql_save(BotAccountDB* self, const struct mmo_bot_account* bot_acc, bool refresh_token); +static bool bot_account_db_sql_load_num(BotAccountDB* self, struct mmo_bot_account* bot_acc, const uint32 account_id); +static bool bot_account_db_sql_load_str(BotAccountDB* self, struct mmo_bot_account* bot_acc, const char* creator_name); +static BotAccountDBIterator* bot_account_db_sql_iterator(BotAccountDB* self); +static void bot_account_db_sql_iter_destroy(BotAccountDBIterator* self); +static bool bot_account_db_sql_iter_next(BotAccountDBIterator* self, struct mmo_bot_account* bot_acc); +static bool mmo_bot_auth_fromsql(BotAccountDB_SQL* db, struct mmo_bot_account* bot_acc, uint32 account_id); +static bool mmo_bot_auth_tosql(BotAccountDB_SQL* db, const struct mmo_bot_account* bot_acc, bool is_new, bool refresh_token); + +/// public constructor +BotAccountDB* bot_account_db_sql(void) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)aCalloc(1, sizeof(BotAccountDB_SQL)); + new(db) BotAccountDB_SQL(); + + // set up the vtable + db->vtable.init = &bot_account_db_sql_init; + db->vtable.destroy = &bot_account_db_sql_destroy; + db->vtable.get_property = &bot_account_db_sql_get_property; + db->vtable.set_property = &bot_account_db_sql_set_property; + db->vtable.save = &bot_account_db_sql_save; + db->vtable.create = &bot_account_db_sql_create; + db->vtable.remove = &bot_account_db_sql_remove; + db->vtable.load_num = &bot_account_db_sql_load_num; + db->vtable.load_str = &bot_account_db_sql_load_str; + db->vtable.iterator = &bot_account_db_sql_iterator; + + // initialize to default values + db->bot_accounts = nullptr; + + // other settings + db->case_sensitive = false; + safestrncpy(db->bot_account_db, "bot_accounts", sizeof(db->bot_account_db)); + safestrncpy(db->login_account_db, "login", sizeof(db->login_account_db)); + //safestrncpy(db->global_acc_reg_num_table, "global_acc_reg_num", sizeof(db->global_acc_reg_num_table)); + //safestrncpy(db->global_acc_reg_str_table, "global_acc_reg_str", sizeof(db->global_acc_reg_str_table)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool bot_account_db_sql_init(BotAccountDB* self) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle; + + db->bot_accounts = Sql_Malloc(); + sql_handle = db->bot_accounts; + + if (SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str())) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->bot_accounts); + db->bot_accounts = nullptr; + return false; + } + + if (!db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str())) + Sql_ShowDebug(sql_handle); + + //self->remove_webtokens( self ); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void bot_account_db_sql_destroy(BotAccountDB* self){ + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + + //if( SQL_ERROR == Sql_Query( db->bot_accounts, "UPDATE `%s` SET `web_auth_token` = NULL", db->bot_account_db ) ){ + // Sql_ShowDebug( db->accounts ); + //} + + Sql_Free(db->bot_accounts); + db->bot_accounts = nullptr; + + db->~BotAccountDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_get_property(BotAccountDB* self, const char* key, char* buf, size_t buflen) +{ + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "bot_account_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->bot_account_db); + //else + //if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + // safesnprintf(buf, buflen, "%s", db->global_acc_reg_str_table); + //else + //if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + // safesnprintf(buf, buflen, "%s", db->global_acc_reg_num_table); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_set_property(BotAccountDB* self, const char* key, const char* value) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "bot_account_db") == 0 ) + safestrncpy(db->bot_account_db, value, sizeof(db->bot_account_db)); + else + if (strcmpi(key, "login_account_db") == 0) + safestrncpy(db->login_account_db, value, sizeof(db->login_account_db)); + //else + //if( strcmpi(key, "global_acc_reg_str_table") == 0 ) + // safestrncpy(db->global_acc_reg_str_table, value, sizeof(db->global_acc_reg_str_table)); + //else + //if( strcmpi(key, "global_acc_reg_num_table") == 0 ) + // safestrncpy(db->global_acc_reg_num_table, value, sizeof(db->global_acc_reg_num_table)); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Create a new account entry. + * acc->account_id = -1 means auto_decidet by server + * bot_acc->account_id is always matching its login table counter part, + * and its value will be written to bot_acc->account_id if everything succeeds. + * @param self: pointer to db + * @param acc: pointer of mmo_bot_account to save + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_create(BotAccountDB* self, struct mmo_bot_account* bot_acc) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle_bot = db->bot_accounts; + + // validate account id to assign + uint32 account_id; + account_id = bot_acc->account_id; + + // 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 + bot_acc->account_id = account_id; + return mmo_bot_auth_tosql(db, bot_acc, true, false); +} + +/** + * Delete an existing bot account entry and its regs. + * @param self: pointer to db + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_remove(BotAccountDB* self, const uint32 account_id) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle = db->bot_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->bot_account_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 bot_account with the new data provided. + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to save + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_save(BotAccountDB* self, const struct mmo_bot_account* bot_acc, bool refresh_token) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + return mmo_bot_auth_tosql(db, bot_acc, false, refresh_token); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to mmo_bot_auth_fromsql. + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to fill + * @param account_id: id of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_load_num(BotAccountDB* self, struct mmo_bot_account* bot_acc, const uint32 account_id) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + return mmo_bot_auth_fromsql(db, bot_acc, account_id); +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Doesn't actually retrieve data yet: escapes and checks userid, then transforms it to accid for fetching. + * Filled data structure is done by delegation to bot_account_db_sql_load_num. + * This function basicly fetches account_ids of all bots of an account + * @param self: pointer to db + * @param bot_acc: pointer of mmo_bot_account to fill + * @param userid: name of user account + * @return true if successful, false if something has failed + */ +static bool bot_account_db_sql_load_str(BotAccountDB* self, struct mmo_bot_account* bot_acc, const char* userid) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + Sql* sql_handle = db->bot_accounts; + char esc_userid[2*NAME_LENGTH+1]; + uint32 account_id; + char* data; + + Sql_EscapeString(sql_handle, esc_userid, userid); + + // get the list of account IDs for this userid + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `account_id` FROM `%s` WHERE `userid`= %s '%s'", + db->login_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("bot_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; + } + + //TODO FIXME this will only get the first found bot to a creator (Currently not a problem, because only one bot per account) + Sql_GetData(sql_handle, 0, &data, nullptr); + account_id = atoi(data); + + return bot_account_db_sql_load_num(self, bot_acc, account_id); +} + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static BotAccountDBIterator* bot_account_db_sql_iterator(BotAccountDB* self) { + BotAccountDB_SQL* db = (BotAccountDB_SQL*)self; + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)aCalloc(1, sizeof(BotAccountDBIterator_SQL)); + + // set up the vtable + iter->vtable.destroy = &bot_account_db_sql_iter_destroy; + iter->vtable.next = &bot_account_db_sql_iter_next; + + // fill data + iter->db = db; + iter->last_bot_account_id = -1; + + return &iter->vtable; +} + +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void bot_account_db_sql_iter_destroy(BotAccountDBIterator* self) { + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next account in the database. + * @param self: pointer to db iterator + * @param bot_acc: pointer of mmo_bot_account to fill + * @return true if next account found and filled, false if something has failed + */ +static bool bot_account_db_sql_iter_next(BotAccountDBIterator* self, struct mmo_bot_account* bot_acc) { + BotAccountDBIterator_SQL* iter = (BotAccountDBIterator_SQL*)self; + BotAccountDB_SQL* db = iter->db; + Sql* sql_handle = db->bot_accounts; + 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->bot_account_db, iter->last_bot_account_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) && + SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) && + data != nullptr ) + {// get account data + uint32 account_id; + account_id = atoi(data); + if( mmo_bot_auth_fromsql(db, bot_acc, account_id) ) + { + iter->last_bot_account_id = account_id; + Sql_FreeResult(sql_handle); + return true; + } + } + Sql_FreeResult(sql_handle); + return false; +} + +/** + * Fetch a struct mmo_bot_account from sql. + * @param db: pointer to db + * @param acc: pointer of mmo_bot_account to fill + * @param account_id: id of user account to take data from + * @return true if successful, false if something has failed + */ +static bool mmo_bot_auth_fromsql(BotAccountDB_SQL* db, struct mmo_bot_account* bot_acc, uint32 account_id) { + Sql* sql_handle = db->bot_accounts; + char* data; + + // retrieve login entry for the specified account + if( SQL_ERROR == Sql_Query(sql_handle, +#ifdef VIP_ENABLE + "SELECT `account_id`,`is_bot`,`creator_id`,`creator_name` FROM `%s` WHERE `account_id` = %d", +#else + "SELECT `account_id`,`is_bot`,`creator_id`,`creator_name` FROM `%s` WHERE `account_id` = %d", +#endif + db->bot_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, nullptr); bot_acc->account_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, nullptr); bot_acc->is_bot = (uint8)atoi(data); + Sql_GetData(sql_handle, 2, &data, nullptr); bot_acc->creator_id = atoi(data); + Sql_GetData(sql_handle, 3, &data, nullptr); safestrncpy(bot_acc->creator_name, data, sizeof(bot_acc->creator_name)); +//ifdef VIP_ENABLE + +//endif + Sql_FreeResult(sql_handle); + + return true; +} + +/** + * Save a struct mmo_bot_account in sql. + * @param db: pointer to db + * @param bot_acc: pointer of mmo_account to save + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool mmo_bot_auth_tosql(BotAccountDB_SQL* db, const struct mmo_bot_account* bot_acc, bool is_new, bool refresh_token) { + Sql* sql_handle = db->bot_accounts; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // 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, +#ifdef VIP_ENABLE + "INSERT INTO `%s` (`account_id`, `is_bot`, `creator_id`, `creator_name`) VALUES (?, ?, ?, ?)", +#else + "INSERT INTO `%s` (`account_id`, `is_bot`, `creator_id`, `creator_name`) VALUES (?, ?, ?, ?)", +#endif + db->bot_account_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)&bot_acc->account_id, sizeof(bot_acc->account_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_SHORT, (void*)&bot_acc->is_bot, sizeof(bot_acc->is_bot)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, (void*)&bot_acc->creator_id, sizeof(bot_acc->creator_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_STRING, (void*)&bot_acc->creator_name, strlen(bot_acc->creator_name)) +//ifdef VIP_ENABLE +//endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + {// update is bot state (meaning if controlled by sindri or by other means) + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "UPDATE `%s` SET `is_bot`=? WHERE `account_id` = '%d'", +#else + "UPDATE `%s` SET `is_bot`=? WHERE `account_id` = '%d'", +#endif + db->bot_account_db, bot_acc->account_id) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_STRING, (void*)bot_acc->is_bot, sizeof(bot_acc->is_bot)) +#ifdef VIP_ENABLE +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + 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; +} + +/* +void mmo_save_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + uint16 count = RFIFOW(fd, 12); + + if (count) { + int cursor = 14, i; + char key[32], sval[254], esc_key[32*2+1], esc_sval[254*2+1]; + + for (i = 0; i < count; i++) { + uint32 index; + safestrncpy(key, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + Sql_EscapeString(sql_handle, esc_key, key); + cursor += RFIFOB(fd, cursor) + 1; + + index = RFIFOL(fd, cursor); + cursor += 4; + + switch (RFIFOB(fd, cursor++)) { + // int + case 0: + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%" PRId64 "')", db->global_acc_reg_num_table, account_id, esc_key, index, RFIFOQ(fd, cursor)) ) + Sql_ShowDebug(sql_handle); + cursor += 8; + break; + case 1: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_num_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + // str + case 2: + safestrncpy(sval, RFIFOCP(fd, cursor + 1), RFIFOB(fd, cursor)); + cursor += RFIFOB(fd, cursor) + 1; + Sql_EscapeString(sql_handle, esc_sval, sval); + if( SQL_ERROR == Sql_Query(sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%" PRIu32 "','%s','%" PRIu32 "','%s')", db->global_acc_reg_str_table, account_id, esc_key, index, esc_sval) ) + Sql_ShowDebug(sql_handle); + break; + case 3: + if( SQL_ERROR == Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%" PRIu32 "' AND `key` = '%s' AND `index` = '%" PRIu32 "' LIMIT 1", db->global_acc_reg_str_table, account_id, esc_key, index) ) + Sql_ShowDebug(sql_handle); + break; + default: + ShowError("mmo_save_global_accreg: unknown type %d\n",RFIFOB(fd, cursor - 1)); + return; + } + } + } +}*/ +/* +void mmo_send_global_accreg(AccountDB* self, int fd, uint32 account_id, uint32 char_id) { + Sql* sql_handle = ((AccountDB_SQL*)self)->accounts; + AccountDB_SQL* db = (AccountDB_SQL*)self; + char* data; + int16 plen = 0; + size_t len; + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_str_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + // + // * Vessel! + // * + // * str type + // * { keyLength(B), key(), index(L), valLength(B), val() } + // + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 254 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 1; // is string type + WFIFOW(fd, 14) = 0; // count + plen = 16; + } + } + + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); + + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%" PRIu32 "'", db->global_acc_reg_num_table, account_id) ) + Sql_ShowDebug(sql_handle); + + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + plen = 16; + + // + // * Vessel! + // * + // * int type + // * { keyLength(B), key(), index(L), value(L) } + // + while ( SQL_SUCCESS == Sql_NextRow(sql_handle) ) { + Sql_GetData(sql_handle, 0, &data, nullptr); + len = strlen(data)+1; + + WFIFOB(fd, plen) = (unsigned char)len; // won't be higher; the column size is 32 + plen += 1; + + safestrncpy(WFIFOCP(fd,plen), data, len); + plen += static_cast( len ); + + Sql_GetData(sql_handle, 1, &data, nullptr); + + WFIFOL(fd, plen) = (uint32)atol(data); + plen += 4; + + Sql_GetData(sql_handle, 2, &data, nullptr); + + WFIFOQ(fd, plen) = strtoll(data,nullptr,10); + plen += 8; + + WFIFOW(fd, 14) += 1; + + if( plen > 60000 ) { + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + // prepare follow up + WFIFOHEAD(fd, 60000 + 300); + WFIFOW(fd, 0) = 0x2726; + // 0x2 = length, set prior to being sent + WFIFOL(fd, 4) = account_id; + WFIFOL(fd, 8) = char_id; + WFIFOB(fd, 12) = 0; // var type (only set when all vars have been sent, regardless of type) + WFIFOB(fd, 13) = 0; // is int type + WFIFOW(fd, 14) = 0; // count + + plen = 16; + } + } + + WFIFOB(fd, 12) = 1; + WFIFOW(fd, 2) = plen; + WFIFOSET(fd, plen); + + Sql_FreeResult(sql_handle); +}*/ + +/* +bool account_db_sql_enable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '1' WHERE `account_id` = '%u'", db->account_db, account_id ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +}*/ + +/** + * Timered function to disable webtoken for user + * If the user is online, then they must have logged since we started the timer. + * In that case, do nothing. The new authtoken must be valid. + * @param tid: timer id + * @param tick: tick of execution + * @param id: user account id + * @param data: BotAccountDB // because we don't use singleton??? + * @return :0 + +TIMER_FUNC(account_disable_webtoken_timer){ + const struct online_login_data* p = login_get_online_user(id); + AccountDB_SQL* db = reinterpret_cast(data); + + if (p == nullptr) { + ShowInfo("Web Auth Token for account %d was disabled\n", id); + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token_enabled` = '0' WHERE `account_id` = '%u'", db->account_db, id ) ){ + Sql_ShowDebug( db->accounts ); + return 0; + } + } + + return 0; +} +*/ + +/* +bool account_db_sql_disable_webtoken( AccountDB* self, const uint32 account_id ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + //add_timer(gettick() + brokk_config.disable_webtoken_delay, account_disable_webtoken_timer, account_id, reinterpret_cast(db)); + + return true; +}*/ +/* +bool account_db_sql_remove_webtokens( AccountDB* self ){ + AccountDB_SQL* db = (AccountDB_SQL*)self; + + if( SQL_ERROR == Sql_Query( db->accounts, "UPDATE `%s` SET `web_auth_token` = NULL, `web_auth_token_enabled` = '0'", db->account_db ) ){ + Sql_ShowDebug( db->accounts ); + return false; + } + + return true; +}*/ diff --git a/src/bot/Brokk/config_helpers.hpp b/src/bot/Brokk/config_helpers.hpp new file mode 100644 index 0000000..1e22e42 --- /dev/null +++ b/src/bot/Brokk/config_helpers.hpp @@ -0,0 +1,676 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef CONFIG_HELPERS_HPP +#define CONFIG_HELPERS_HPP +/* +#include +#include +#include +#include +*/ +/*Inter Configs +struct inter_conf { + uint32 start_status_points; + bool emblem_woe_change; + uint32 emblem_transparency_limit; +}; +extern struct inter_conf inter_config; +*/ + +//Brokk Configs +/* +struct Brokk_Config { + uint32 brokk_ip; /// the address to bind to + uint16 brokk_port; /// the port to bind to + bool log_brokk; /// whether to log brokk server actions or not + char date_format[32]; /// date format used in messages + bool console; /// console input system enabled? + + char brokkconf_name[256]; /// name of main config file + char msgconf_name[256]; /// name of msg_conf config file + bool destroy_all_on_shutdown; /// cleanup all bots on shutdown? + uint32 allowed_bot_creations; /// number of allowed bot creations in quick succession + uint8 acc_name_min_length; /// minimum account name length + uint8 password_min_length; /// minimum password length + int time_allowed; /// registration interval in seconds + + bool use_web_auth_token; /// Enable web authentication token system + int disable_webtoken_delay; /// delay disabling web token after char logs off in milliseconds + +#ifdef VIP_ENABLE + +#endif + +}; +extern struct Brokk_Config brokk_config; +*/ + +bool config_read(const char* cfgName, bool normal); +void set_defaults(); + +//Battle Configs +#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 +#define MIN_BODY_STYLE battle_config.min_body_style +#define MAX_BODY_STYLE battle_config.max_body_style +/* +struct Battle_Config +{ + int warp_point_debug; + int enable_critical; + int mob_critical_rate; + int critical_rate; + int enable_baseatk, enable_baseatk_renewal; + 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 cardfix_monster_physical; + 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 guild_skill_relog_type; + 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_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 pet_unequip_destroy; + int pet_master_dead; + + 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 hanbok_ignorepalette; + int oktoberfest_ignorepalette; + int natural_healhp_interval; + int natural_healsp_interval; + int natural_heal_skill_interval; + int natural_heal_weight_rate; + int natural_heal_weight_rate_renewal; + int arrow_decrement; + int ammo_unequip; + int ammo_check_weapon; + int max_aspd; + int max_walk_speed; //Maximum walking speed after buffs [Skotlex] + int max_hp_lv99; + int max_hp_lv150; + 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 vending_tax_min; + 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 item_rate_common_mvp, item_rate_heal_mvp, item_rate_use_mvp, item_rate_equip_mvp, item_rate_card_mvp; + + 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_mvp_mode; //rAthena addition [Playtester] + 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 prevent_logout_trigger; + int land_protector_behavior; + int npc_emotion_behavior; + + 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 multi_level_up_base; + int multi_level_up_job; + int max_exp_gain_rate; //Max amount of exp bar % you can get in one go. + int pk_mode; + int pk_mode_mes; + 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 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 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 skill_steal_random_options; + 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 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 drop_rate_cap; // Drop rate can't be raised above this amount by drop bonus items + int drop_rate_cap_vip; + + 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 official_cell_stack_limit; // [Playtester] + int custom_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_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 knockback_left; + 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; + int hom_S_growth_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 max_trans_parameter; + int max_third_trans_parameter; + int max_extended_parameter; + int max_summoner_parameter; + int max_fourth_parameter; + int max_third_aspd; + int max_summoner_aspd; + int vcast_stat_scale; + + int mvp_tomb_enabled; + int mvp_tomb_delay; + + int atcommand_suggestions_enabled; + int min_npc_vendchat_distance; + int atcommand_mobinfo_type; + + int mob_size_influence; // Enable modifications on earned experience, drop rates and monster status depending on monster size. [mkbu95] + int skill_trap_type; + int allow_consume_restricted_item; + int allow_equip_restricted_item; + int max_walk_path; + int item_enabled_npc; + int item_onfloor; // Whether to drop an undroppable item on the map or destroy it if inventory is full. + int bowling_bash_area; + int drop_rateincrease; + int feature_auction; + int feature_banking; + int vip_storage_increase; + int vip_base_exp_increase; + int vip_job_exp_increase; + int vip_zeny_penalty; + int vip_bm_increase; + int vip_drop_increase; + int vip_gemstone; + int vip_exp_penalty_base; + int vip_exp_penalty_job; + int vip_disp_rate; + int mon_trans_disable_in_gvg; + int discount_item_point_shop; + int update_enemy_position; + int devotion_rdamage; + int feature_itemlink; + int feature_mesitemlink; + int feature_mesitemlink_brackets; + int feature_mesitemlink_dbname; + + // autotrade persistency + int feature_autotrade; + int feature_autotrade_direction; + int feature_autotrade_head_direction; + int feature_autotrade_sit; + int feature_autotrade_open_delay; + + // Fame points + int fame_taekwon_mission; + int fame_refine_lv1; + int fame_refine_lv2; + int fame_refine_lv3; + int fame_forge; + int fame_pharmacy_3; + int fame_pharmacy_5; + int fame_pharmacy_7; + int fame_pharmacy_10; + + int disp_servervip_msg; + int warg_can_falcon; + int path_blown_halt; + int rental_mount_speed_boost; + int warp_suggestions_enabled; + int taekwon_mission_mobname; + int teleport_on_portal; + int cart_revo_knockback; + int guild_notice_changemap; + int transcendent_status_points; + int taekwon_ranker_min_lv; + int revive_onwarp; + int mail_delay; + int autotrade_monsterignore; + int idletime_option; + int spawn_direction; + int arrow_shower_knockback; + int devotion_rdamage_skill_only; + int max_extended_aspd; + int mob_chase_refresh; //How often a monster should refresh its chase [Playtester] + int mob_icewall_walk_block; //How a normal monster should be trapped in icewall [Playtester] + int boss_icewall_walk_block; //How a boss monster should be trapped in icewall [Playtester] + int snap_dodge; // Enable or disable dodging damage snapping away [csnv] + int stormgust_knockback; + int default_fixed_castrate; + int default_bind_on_equip; + int pet_ignore_infinite_def; // Makes fixed damage of petskillattack2 ignores infinite defense + int homunculus_evo_intimacy_need; + int homunculus_evo_intimacy_reset; + int monster_loot_search_type; + int feature_roulette; + int feature_roulette_bonus_reward; + int monster_hp_bars_info; + int min_body_style; + int max_body_style; + int save_body_style; + int mob_eye_range_bonus; //Vulture's Eye and Snake's Eye range bonus + int mob_stuck_warning; //Show warning if a monster is stuck too long + int skill_eightpath_algorithm; //Official path algorithm + int skill_eightpath_same_cell; + int death_penalty_maxlv; + int exp_cost_redemptio; + int exp_cost_redemptio_limit; + int mvp_exp_reward_message; + int can_damage_skill; //Which BL types can damage traps + int atcommand_levelup_events; + int atcommand_disable_npc; + int block_account_in_same_party; + int tarotcard_equal_chance; //Official or equal chance for each card + int change_party_leader_samemap; + int dispel_song; //Can songs be dispelled? + int guild_maprespawn_clones; // Should clones be killed by maprespawnguildid? + int hide_fav_sell; + int mail_daily_count; + int mail_zeny_fee; + int mail_attachment_price; + int mail_attachment_weight; + int banana_bomb_duration; + int guild_leaderchange_delay; + int guild_leaderchange_woe; + int guild_alliance_onlygm; + int feature_achievement; + int allow_bound_sell; + int autoloot_adjust; + int feature_petevolution; + int feature_pet_autofeed; + int feature_pet_autofeed_rate; + int pet_autofeed_always; + int broadcast_hide_name; + int skill_drop_items_full; + int switch_remove_edp; + int feature_homunculus_autofeed; + int feature_homunculus_autofeed_rate; + int summoner_race; + int summoner_size; + int homunculus_autofeed_always; + int feature_attendance; + int feature_privateairship; + int rental_transaction; + int min_shop_buy; + int min_shop_sell; + int feature_equipswitch; + int pet_walk_speed; + int blacksmith_fame_refine_threshold; + int mob_nopc_idleskill_rate; + int mob_nopc_move_rate; + int boss_nopc_idleskill_rate; + int boss_nopc_move_rate; + int hom_idle_no_share; + int idletime_hom_option; + int devotion_standup_fix; + int feature_bgqueue; + int bgqueue_nowarp_mapflag; + int homunculus_exp_gain; + int rental_item_novalue; + int ping_timer_interval; + int ping_time; + int show_skill_scale; + int achievement_mob_share; + int slave_stick_with_master; + int at_logout_event; + int homunculus_starving_rate; + int homunculus_starving_delay; + int drop_connection_on_quit; + int mob_spawn_variance; + int mercenary_autoloot; + int mer_idle_no_share; + int idletime_mer_option; + int feature_refineui; + int rndopt_drop_pillar; + int pet_legacy_formula; + int pet_distance_check; + int pet_hide_check; + + int instance_block_leave; + int instance_block_leaderchange; + int instance_block_invite; + int instance_block_expulsion; + // 4th Jobs Stuff + int trait_points_job_change; + int use_traitpoint_table; + int max_trait_parameter; + int max_res_mres_ignored; + int max_ap; + int ap_rate; + int restart_ap_rate; + int loose_ap_on_death; + int loose_ap_on_map; + int keep_ap_on_logout; + int attack_machine_level_difference; + + int feature_barter; + int feature_barter_extended; + int break_mob_equip; + int macro_detection_retry; + int macro_detection_timeout; + int macro_detection_punishment; + int macro_detection_punishment_time; + + int feature_dynamicnpc_timeout; + int feature_dynamicnpc_rangex; + int feature_dynamicnpc_rangey; + int feature_dynamicnpc_direction; + + int mob_respawn_time; + int mob_unlock_time; + int map_edge_size; + int randomize_center_cell; + + int feature_stylist; + int feature_banking_state_enforce; + int instance_allow_reconnect; + int synchronize_damage; + int item_stacking; + int hom_delay_reset_vaporize; + int hom_delay_reset_warp; + +#include +}; + +extern struct Battle_Config battle_config; +*/ +#endif /* CONFIG_HELPERS_HPP */ diff --git a/src/bot/Brokk/helper_skills.cpp b/src/bot/Brokk/helper_skills.cpp new file mode 100644 index 0000000..641910c --- /dev/null +++ b/src/bot/Brokk/helper_skills.cpp @@ -0,0 +1,2425 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + + +#include "helper_skills.hpp" + +#include //min / max +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "brokk.hpp" // brokk_config + +/// global defines + +/// internal structure +typedef struct SkillDB_SQL { + SkillDB vtable; // public interface + Sql* bot_skills; // SQL handle bot skill storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = "ragnarok"; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char skill_db[32]; + +} SkillDB_SQL; + +/// internal structure +typedef struct SkillDBIterator_SQL { + SkillDBIterator vtable; // public interface + SkillDB_SQL* db; + int char_id; + int last_skill_id; +} SkillDBIterator_SQL; + +/// internal functions +static bool bot_skill_db_sql_init(SkillDB* self); +static void bot_skill_db_sql_destroy(SkillDB* self); +static bool bot_skill_db_sql_get_property(SkillDB* self, const char* key, char* buf, size_t buflen); +static bool bot_skill_db_sql_set_property(SkillDB* self, const char* option, const char* value); +static bool bot_skill_db_sql_create(SkillDB* self, struct bot_skill* skill); +static bool bot_skill_db_sql_remove(SkillDB* self, const uint32 char_id, const uint32 skill_id); +//static bool bot_skill_db_sql_save(SkillDB* self, const struct bot_skill* skill); +static bool bot_skill_db_sql_load_num(SkillDB* self, struct bot_skill* skill, const uint32 char_id, const uint32 skill_id); +static SkillDBIterator* bot_skill_db_sql_iterator(SkillDB* self, const uint32 char_id); +static void bot_skill_db_sql_iter_destroy(SkillDBIterator* self); +static bool bot_skill_db_sql_iter_next(SkillDBIterator* self, const uint32 char_id, struct bot_skill* skill); +static bool bot_skill_db_fromsql(SkillDB_SQL* db, struct bot_skill* skill, uint32 char_id, uint32 skill_id); +static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, const uint32 char_id, bool is_new); + +/// public constructor +SkillDB* skill_db_sql(void) { + SkillDB_SQL* db = (SkillDB_SQL*)aCalloc(1, sizeof(SkillDB_SQL)); + new(db) SkillDB_SQL(); + + // set up the vtable + db->vtable.init = &bot_skill_db_sql_init; + db->vtable.destroy = &bot_skill_db_sql_destroy; + db->vtable.get_property = &bot_skill_db_sql_get_property; + db->vtable.set_property = &bot_skill_db_sql_set_property; + //db->vtable.save = &bot_skill_db_sql_save; + db->vtable.create = &bot_skill_db_sql_create; + db->vtable.remove = &bot_skill_db_sql_remove; + db->vtable.load_num = &bot_skill_db_sql_load_num; + db->vtable.iterator = &bot_skill_db_sql_iterator; + + // initialize to default values + db->bot_skills = nullptr; + + // other settings + db->case_sensitive = false; + safestrncpy(db->skill_db, "skill", sizeof(db->skill_db)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool bot_skill_db_sql_init(SkillDB* self) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + Sql* sql_handle; + + db->bot_skills = Sql_Malloc(); + sql_handle = db->bot_skills; + + if (SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str())) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->bot_skills); + db->bot_skills = nullptr; + return false; + } + + if (!db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str())) + Sql_ShowDebug(sql_handle); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void bot_skill_db_sql_destroy(SkillDB* self){ + SkillDB_SQL* db = (SkillDB_SQL*)self; + + + Sql_Free(db->bot_skills); + db->bot_skills = nullptr; + + db->~SkillDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_sql_get_property(SkillDB* self, const char* key, char* buf, size_t buflen) +{ + SkillDB_SQL* db = (SkillDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "skill_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->skill_db); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_sql_set_property(SkillDB* self, const char* key, const char* value) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "skill_db") == 0 ) + safestrncpy(db->skill_db, value, sizeof(db->skill_db)); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Create a new skill entry. + * bot_skill->char_id & -> skill_id are primary keys, + * and its values will be written to bot_skills + * @param self: pointer to db + * @param skill: pointer of bot_skill to save + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_sql_create(SkillDB* self, struct bot_skill* skill) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + Sql* sql_handle_bot = db->bot_skills; + + // validate char id and skill_id to assign + uint32 char_id, skill_id; + char_id = skill->char_id; + skill_id = skill->skill_id; + + // zero value is prohibited + if(char_id == 0 || skill_id == 0 ) + return false; + + return bot_skill_db_tosql(db, skill, char_id, true); +} + +/** + * Delete an existing bot skill entry. + * @param self: pointer to db + * @param char_id: id of char, the skill belongs to + * @param skill_id: if of dkill to delete + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_sql_remove(SkillDB* self, const uint32 char_id, const uint32 skill_id) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + Sql* sql_handle = db->bot_skills; + bool result = false; + + if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") + || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = %d AND `id` = %d", db->skill_db, char_id, skill_id) + ) + Sql_ShowDebug(sql_handle); + else + result = true; + + result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); + + return result; +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to bot_skill_db_fromsql. + * @param self: pointer to db + * @param bot_skill: pointer of bot_skill to fill + * @param char_id: id of character + * @param skill_id: id of skill + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_sql_load_num(SkillDB* self, struct bot_skill* skill, const uint32 char_id, const uint32 skill_id) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + return bot_skill_db_fromsql(db, skill, char_id, skill_id); +} + + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static SkillDBIterator* bot_skill_db_sql_iterator(SkillDB* self, const uint32 char_id) { + SkillDB_SQL* db = (SkillDB_SQL*)self; + SkillDBIterator_SQL* iter = (SkillDBIterator_SQL*)aCalloc(1, sizeof(SkillDBIterator_SQL)); + + // set up the vtable + iter->vtable.destroy = &bot_skill_db_sql_iter_destroy; + iter->vtable.next = &bot_skill_db_sql_iter_next; + + // fill data + iter->db = db; + iter->char_id = char_id; + iter->last_skill_id = -1; + + return &iter->vtable; +} + +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void bot_skill_db_sql_iter_destroy(SkillDBIterator* self) { + SkillDBIterator_SQL* iter = (SkillDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next skill entry in the database. + * @param self: pointer to db iterator + * @param char_id: char_id of the bot the skills belong to + * @param skill: pointer of bot_skill to fill + * @return true if next skill entry found and filled, false if something has failed + */ +static bool bot_skill_db_sql_iter_next(SkillDBIterator* self, const uint32 char_id, struct bot_skill* skill) { + SkillDBIterator_SQL* iter = (SkillDBIterator_SQL*)self; + SkillDB_SQL* db = iter->db; + Sql* sql_handle = db->bot_skills; + char* data; + + // get next account ID + if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id` FROM `%s` WHERE `char_id` = %d AND `id` > '%d' ORDER BY `id` ASC LIMIT 1", + db->skill_db, iter->char_id, iter->last_skill_id) ) + { + Sql_ShowDebug(sql_handle); + return false; + } + + if( SQL_SUCCESS == Sql_NextRow(sql_handle) && + SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) && + data != nullptr ) + {// get skill data + uint32 skill_id; + skill_id = atoi(data); + if( bot_skill_db_fromsql(db, skill, char_id, skill_id) ) + { + iter->last_skill_id = skill_id; + Sql_FreeResult(sql_handle); + return true; + } + } + Sql_FreeResult(sql_handle); + return false; +} + +/** + * Fetch a struct bot_skill from sql. + * @param db: pointer to db + * @param skill: pointer of bot_skill to fill + * @param char_id: id of character, skills belong to + * @param skill_id: id of skill to take data from + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_fromsql(SkillDB_SQL* db, struct bot_skill* skill, uint32 char_id, uint32 skill_id) { + Sql* sql_handle = db->bot_skills; + char* data; + + // retrieve login entry for the specified account + if( SQL_ERROR == Sql_Query(sql_handle, +#ifdef VIP_ENABLE + "SELECT `id`,`lv`,`flag` FROM `%s` WHERE `char_id` = %d AND `id` = %d", +#else + "SELECT `id`,`lv`,`flag` FROM `%s` WHERE `char_id` = %d AND `id` = %d", +#endif + db->skill_db, char_id, skill_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, nullptr); skill->skill_id = atoi(data); + Sql_GetData(sql_handle, 1, &data, nullptr); skill->level = atoi(data); + Sql_GetData(sql_handle, 2, &data, nullptr); skill->flag = (uint8)atoi(data); + +//endif + Sql_FreeResult(sql_handle); + + return true; +} + +/** + * Save a struct bot_skill in sql. + * @param db: pointer to db + * @param bot_skill: pointer of bot_skill to save + * @üaram char_id: char_id of the character the skill belongs to + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, const uint32 char_id, bool is_new) { + Sql* sql_handle = db->bot_skills; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // 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, +#ifdef VIP_ENABLE + "INSERT INTO `%s` (`char_id`, `id`, `lv`, `flag`) VALUES (?, ?, ?, ?)", +#else + "INSERT INTO `%s` (`char_id`, `id`, `lv`, `flag`) VALUES (?, ?, ?, ?)", +#endif + db->skill_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)&skill->char_id, sizeof(skill->char_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, (void*)&skill->skill_id, sizeof(skill->skill_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_INT, (void*)&skill->level, sizeof(skill->level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_SHORT, (void*)&skill->flag, sizeof(skill->flag)) +//ifdef VIP_ENABLE +//endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + {// update is bot state (meaning if controlled by sindri or by other means) + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "UPDATE `%s` SET `lv`=?, `flag`=? WHERE `char_id` = '%d' AND `id` = '%d'", +#else + "UPDATE `%s` SET `lv`=?, `flag`=? WHERE `char_id` = '%d' AND `id` = '%d'", +#endif + db->skill_db, skill->char_id, skill->skill_id) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_INT, (void*)skill->level, sizeof(skill->level)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_INT, (void*)skill->flag, sizeof(skill->flag)) +#ifdef VIP_ENABLE +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + 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; +} + +const char* convert_skill_id_to_str(int skill_id) { + // Create a map for the skill enum values to the corresponding skill strings + static const std::unordered_map skill_map = { + //Basic skill + {NV_BASIC ,"NV_BASIC" }, + //Swordman class + {SM_SWORD ,"SM_SWORD" }, + {SM_TWOHAND ,"SM_TWOHAND" }, + {SM_RECOVERY ,"SM_RECOVERY" }, + {SM_BASH ,"SM_BASH" }, + {SM_PROVOKE ,"SM_PROVOKE" }, + {SM_MAGNUM ,"SM_MAGNUM" }, + {SM_ENDURE ,"SM_ENDURE" }, + + //Mage class + {MG_SRECOVERY ,"MG_SRECOVERY" }, + {MG_SIGHT ,"MG_SIGHT" }, + {MG_NAPALMBEAT ,"MG_NAPALMBEAT" }, + {MG_SAFETYWALL ,"MG_SAFETYWALL" }, + {MG_SOULSTRIKE ,"MG_SOULSTRIKE" }, + {MG_COLDBOLT ,"MG_COLDBOLT" }, + {MG_FROSTDIVER ,"MG_FROSTDIVER" }, + {MG_STONECURSE ,"MG_STONECURSE" }, + {MG_FIREBALL ,"MG_FIREBALL" }, + {MG_FIREWALL ,"MG_FIREWALL" }, + {MG_FIREBOLT ,"MG_FIREBOLT" }, + {MG_LIGHTNINGBOLT ,"MG_LIGHTNINGBOLT" }, + {MG_THUNDERSTORM ,"MG_THUNDERSTORM" }, + + //Acolyte class + {AL_DP ,"AL_DP" }, + {AL_DEMONBANE ,"AL_DEMONBANE" }, + {AL_RUWACH ,"AL_RUWACH" }, + {AL_PNEUMA ,"AL_PNEUMA" }, + {AL_TELEPORT ,"AL_TELEPORT" }, + {AL_WARP ,"AL_WARP" }, + {AL_HEAL ,"AL_HEAL" }, + {AL_INCAGI ,"AL_INCAGI" }, + {AL_DECAGI ,"AL_DECAGI" }, + {AL_HOLYWATER ,"AL_HOLYWATER" }, + {AL_CRUCIS ,"AL_CRUCIS" }, + {AL_ANGELUS ,"AL_ANGELUS" }, + {AL_BLESSING ,"AL_BLESSING" }, + {AL_CURE ,"AL_CURE" }, + + //Merchant class + {MC_INCCARRY ,"MC_INCCARRY" }, + {MC_DISCOUNT ,"MC_DISCOUNT" }, + {MC_OVERCHARGE ,"MC_OVERCHARGE" }, + {MC_PUSHCART ,"MC_PUSHCART" }, + {MC_IDENTIFY ,"MC_IDENTIFY" }, + {MC_VENDING ,"MC_VENDING" }, + {MC_MAMMONITE ,"MC_MAMMONITE" }, + + //Archer class + {AC_OWL ,"AC_OWL" }, + {AC_VULTURE ,"AC_VULTURE" }, + {AC_CONCENTRATION ,"AC_CONCENTRATION" }, + {AC_DOUBLE ,"AC_DOUBLE" }, + {AC_SHOWER ,"AC_SHOWER" }, + + //Thief class + {TF_DOUBLE ,"TF_DOUBLE" }, + {TF_MISS ,"TF_MISS" }, + {TF_STEAL ,"TF_STEAL" }, + {TF_HIDING ,"TF_HIDING" }, + {TF_POISON ,"TF_POISON" }, + {TF_DETOXIFY ,"TF_DETOXIFY" }, + + {ALL_RESURRECTION ,"ALL_RESURRECTION" }, + + // Knight class + {KN_SPEARMASTERY ,"KN_SPEARMASTERY" }, + {KN_PIERCE ,"KN_PIERCE" }, + {KN_BRANDISHSPEAR ,"KN_BRANDISHSPEAR" }, + {KN_SPEARSTAB ,"KN_SPEARSTAB" }, + {KN_SPEARBOOMERANG ,"KN_SPEARBOOMERANG" }, + {KN_TWOHANDQUICKEN ,"KN_TWOHANDQUICKEN" }, + {KN_AUTOCOUNTER ,"KN_AUTOCOUNTER" }, + {KN_BOWLINGBASH ,"KN_BOWLINGBASH" }, + {KN_RIDING ,"KN_RIDING" }, + {KN_CAVALIERMASTERY ,"KN_CAVALIERMASTERY" }, + + // Priest class + {PR_MACEMASTERY ,"PR_MACEMASTERY" }, + {PR_IMPOSITIO ,"PR_IMPOSITIO" }, + {PR_SUFFRAGIUM ,"PR_SUFFRAGIUM" }, + {PR_ASPERSIO ,"PR_ASPERSIO" }, + {PR_BENEDICTIO ,"PR_BENEDICTIO" }, + {PR_SANCTUARY ,"PR_SANCTUARY" }, + {PR_SLOWPOISON ,"PR_SLOWPOISON" }, + {PR_STRECOVERY ,"PR_STRECOVERY" }, + {PR_KYRIE ,"PR_KYRIE" }, + {PR_MAGNIFICAT ,"PR_MAGNIFICAT" }, + {PR_GLORIA ,"PR_GLORIA" }, + {PR_LEXDIVINA ,"PR_LEXDIVINA" }, + {PR_TURNUNDEAD ,"PR_TURNUNDEAD" }, + {PR_LEXAETERNA ,"PR_LEXAETERNA" }, + {PR_MAGNUS ,"PR_MAGNUS" }, + + //Wizard class + {WZ_FIREPILLAR ,"WZ_FIREPILLAR" }, + {WZ_SIGHTRASHER ,"WZ_SIGHTRASHER" }, + {WZ_FIREIVY ,"WZ_FIREIVY" }, + {WZ_METEOR ,"WZ_METEOR" }, + {WZ_JUPITEL ,"WZ_JUPITEL" }, + {WZ_VERMILION ,"WZ_VERMILION" }, + {WZ_WATERBALL ,"WZ_WATERBALL" }, + {WZ_ICEWALL ,"WZ_ICEWALL" }, + {WZ_FROSTNOVA ,"WZ_FROSTNOVA" }, + {WZ_STORMGUST ,"WZ_STORMGUST" }, + {WZ_EARTHSPIKE ,"WZ_EARTHSPIKE" }, + {WZ_HEAVENDRIVE ,"WZ_HEAVENDRIVE" }, + {WZ_QUAGMIRE ,"WZ_QUAGMIRE" }, + {WZ_ESTIMATION ,"WZ_ESTIMATION" }, + + //Blacksmith class + {BS_IRON ,"BS_IRON" }, + {BS_STEEL ,"BS_STEEL" }, + {BS_ENCHANTEDSTONE ,"BS_ENCHANTEDSTONE" }, + {BS_ORIDEOCON ,"BS_ORIDEOCON" }, + {BS_DAGGER ,"BS_DAGGER" }, + {BS_SWORD ,"BS_SWORD" }, + {BS_TWOHANDSWORD ,"BS_TWOHANDSWORD" }, + {BS_AXE ,"BS_AXE" }, + {BS_MACE ,"BS_MACE" }, + {BS_KNUCKLE ,"BS_KNUCKLE" }, + {BS_SPEAR ,"BS_SPEAR" }, + {BS_HILTBINDING ,"BS_HILTBINDING" }, + {BS_FINDINGORE ,"BS_FINDINGORE" }, + {BS_WEAPONRESEARCH ,"BS_WEAPONRESEARCH" }, + {BS_REPAIRWEAPON ,"BS_REPAIRWEAPON" }, + {BS_SKINTEMPER ,"BS_SKINTEMPER" }, + {BS_HAMMERFALL ,"BS_HAMMERFALL" }, + {BS_ADRENALINE ,"BS_ADRENALINE" }, + {BS_WEAPONPERFECT ,"BS_WEAPONPERFECT" }, + {BS_OVERTHRUST ,"BS_OVERTHRUST" }, + {BS_MAXIMIZE ,"BS_MAXIMIZE" }, + + // Hunter Class + {HT_SKIDTRAP ,"HT_SKIDTRAP" }, + {HT_LANDMINE ,"HT_LANDMINE", }, + {HT_ANKLESNARE ,"HT_ANKLESNARE" }, + {HT_SHOCKWAVE ,"HT_SHOCKWAVE" }, + {HT_SANDMAN ,"HT_SANDMAN" }, + {HT_FLASHER ,"HT_FLASHER" }, + {HT_FREEZINGTRAP ,"HT_FREEZINGTRAP" }, + {HT_BLASTMINE ,"HT_BLASTMINE" }, + {HT_CLAYMORETRAP ,"HT_CLAYMORETRAP" }, + {HT_REMOVETRAP ,"HT_REMOVETRAP" }, + {HT_TALKIEBOX ,"HT_TALKIEBOX" }, + {HT_BEASTBANE ,"HT_BEASTBANE" }, + {HT_FALCON ,"HT_FALCON" }, + {HT_STEELCROW ,"HT_STEELCROW" }, + {HT_BLITZBEAT ,"HT_BLITZBEAT" }, + {HT_DETECTING ,"HT_DETECTING" }, + {HT_SPRINGTRAP ,"HT_SPRINGTRAP" }, + + //Assasin class + {AS_RIGHT ,"AS_RIGHT" }, + {AS_LEFT ,"AS_LEFT" }, + {AS_KATAR ,"AS_KATAR" }, + {AS_CLOAKING ,"AS_CLOAKING" }, + {AS_SONICBLOW ,"AS_SONICBLOW" }, + {AS_GRIMTOOTH ,"AS_GRIMTOOTH" }, + {AS_ENCHANTPOISON ,"AS_ENCHANTPOISON" }, + {AS_POISONREACT ,"AS_POISONREACT" }, + {AS_VENOMDUST ,"AS_VENOMDUST" }, + {AS_SPLASHER ,"AS_SPLASHER" }, + + //Quest skills 1st classes + {NV_FIRSTAID ,"NV_FIRSTAID" }, + {NV_TRICKDEAD ,"NV_TRICKDEAD" }, + {SM_MOVINGRECOVERY ,"SM_MOVINGRECOVERY" }, + {SM_FATALBLOW ,"SM_FATALBLOW" }, + {SM_AUTOBERSERK ,"SM_AUTOBERSERK" }, + {AC_MAKINGARROW ,"AC_MAKINGARROW" }, + {AC_CHARGEARROW ,"AC_CHARGEARROW" }, + {TF_SPRINKLESAND ,"TF_SPRINKLESAND" }, + {TF_BACKSLIDING ,"TF_BACKSLIDING" }, + {TF_PICKSTONE ,"TF_PICKSTONE" }, + {TF_THROWSTONE ,"TF_THROWSTONE" }, + {MC_CARTREVOLUTION ,"MC_CARTREVOLUTION" }, + {MC_CHANGECART ,"MC_CHANGECART" }, + {MC_LOUD ,"MC_LOUD" }, + {AL_HOLYLIGHT ,"AL_HOLYLIGHT" }, + {MG_ENERGYCOAT ,"MG_ENERGYCOAT" }, + + //NPC Skills + {NPC_PIERCINGATT ,"NPC_PIERCINGATT" }, + {NPC_MENTALBREAKER ,"NPC_MENTALBREAKER" }, + {NPC_RANGEATTACK ,"NPC_RANGEATTACK" }, + {NPC_ATTRICHANGE ,"NPC_ATTRICHANGE" }, + {NPC_CHANGEWATER ,"NPC_CHANGEWATER" }, + {NPC_CHANGEGROUND ,"NPC_CHANGEGROUND" }, + {NPC_CHANGEFIRE ,"NPC_CHANGEFIRE" }, + {NPC_CHANGEWIND ,"NPC_CHANGEWIND" }, + {NPC_CHANGEPOISON ,"NPC_CHANGEPOISON" }, + {NPC_CHANGEHOLY ,"NPC_CHANGEHOLY" }, + {NPC_CHANGEDARKNESS ,"NPC_CHANGEDARKNESS" }, + {NPC_CHANGETELEKINESIS ,"NPC_CHANGETELEKINESIS" }, + {NPC_CRITICALSLASH ,"NPC_CRITICALSLASH" }, + {NPC_COMBOATTACK ,"NPC_COMBOATTACK" }, + {NPC_GUIDEDATTACK ,"NPC_GUIDEDATTACK" }, + {NPC_SELFDESTRUCTION ,"NPC_SELFDESTRUCTION" }, + {NPC_SPLASHATTACK ,"NPC_SPLASHATTACK" }, + {NPC_SUICIDE ,"NPC_SUICIDE" }, + {NPC_POISON ,"NPC_POISON" }, + {NPC_BLINDATTACK ,"NPC_BLINDATTACK" }, + {NPC_SILENCEATTACK ,"NPC_SILENCEATTACK" }, + {NPC_STUNATTACK ,"NPC_STUNATTACK" }, + {NPC_PETRIFYATTACK ,"NPC_PETRIFYATTACK" }, + {NPC_CURSEATTACK ,"NPC_CURSEATTACK" }, + {NPC_SLEEPATTACK ,"NPC_SLEEPATTACK" }, + {NPC_RANDOMATTACK ,"NPC_RANDOMATTACK" }, + {NPC_WATERATTACK ,"NPC_WATERATTACK" }, + {NPC_GROUNDATTACK ,"NPC_GROUNDATTACK" }, + {NPC_FIREATTACK ,"NPC_FIREATTACK" }, + {NPC_WINDATTACK ,"NPC_WINDATTACK" }, + {NPC_POISONATTACK ,"NPC_POISONATTACK" }, + {NPC_HOLYATTACK ,"NPC_HOLYATTACK" }, + {NPC_DARKNESSATTACK ,"NPC_DARKNESSATTACK" }, + {NPC_TELEKINESISATTACK ,"NPC_TELEKINESISATTACK" }, + {NPC_MAGICALATTACK ,"NPC_MAGICALATTACK" }, + {NPC_METAMORPHOSIS ,"NPC_METAMORPHOSIS" }, + {NPC_PROVOCATION ,"NPC_PROVOCATION" }, + {NPC_SMOKING ,"NPC_SMOKING" }, + {NPC_SUMMONSLAVE ,"NPC_SUMMONSLAVE" }, + {NPC_EMOTION ,"NPC_EMOTION" }, + {NPC_TRANSFORMATION ,"NPC_TRANSFORMATION" }, + {NPC_BLOODDRAIN ,"NPC_BLOODDRAIN" }, + {NPC_ENERGYDRAIN ,"NPC_ENERGYDRAIN" }, + {NPC_KEEPING ,"NPC_KEEPING" }, + {NPC_DARKBREATH ,"NPC_DARKBREATH" }, + {NPC_DARKBLESSING ,"NPC_DARKBLESSING" }, + {NPC_BARRIER ,"NPC_BARRIER" }, + {NPC_DEFENDER ,"NPC_DEFENDER" }, + {NPC_LICK ,"NPC_LICK" }, + {NPC_HALLUCINATION ,"NPC_HALLUCINATION" }, + {NPC_REBIRTH ,"NPC_REBIRTH" }, + {NPC_SUMMONMONSTER ,"NPC_SUMMONMONSTER" }, + + //Rogue class + {RG_SNATCHER ,"RG_SNATCHER" }, + {RG_STEALCOIN ,"RG_STEALCOIN" }, + {RG_BACKSTAP ,"RG_BACKSTAP" }, + {RG_TUNNELDRIVE ,"RG_TUNNELDRIVE" }, + {RG_RAID ,"RG_RAID" }, + {RG_STRIPWEAPON ,"RG_STRIPWEAPON" }, + {RG_STRIPSHIELD ,"RG_STRIPSHIELD" }, + {RG_STRIPARMOR ,"RG_STRIPARMOR" }, + {RG_STRIPHELM ,"RG_STRIPHELM" }, + {RG_INTIMIDATE ,"RG_INTIMIDATE" }, + {RG_GRAFFITI ,"RG_GRAFFITI" }, + {RG_FLAGGRAFFITI ,"RG_FLAGGRAFFITI" }, + {RG_CLEANER ,"RG_CLEANER" }, + {RG_GANGSTER ,"RG_GANGSTER" }, + {RG_COMPULSION ,"RG_COMPULSION" }, + {RG_PLAGIARISM ,"RG_PLAGIARISM" }, + + //Alchemist class + {AM_AXEMASTERY ,"AM_AXEMASTERY" }, + {AM_LEARNINGPOTION ,"AM_LEARNINGPOTION" }, + {AM_PHARMACY ,"AM_PHARMACY" }, + {AM_DEMONSTRATION ,"AM_DEMONSTRATION" }, + {AM_ACIDTERROR ,"AM_ACIDTERROR" }, + {AM_POTIONPITCHER ,"AM_POTIONPITCHER" }, + {AM_CANNIBALIZE ,"AM_CANNIBALIZE" }, + {AM_SPHEREMINE ,"AM_SPHEREMINE" }, + {AM_CP_WEAPON ,"AM_CP_WEAPON" }, + {AM_CP_SHIELD ,"AM_CP_SHIELD" }, + {AM_CP_ARMOR ,"AM_CP_ARMOR" }, + {AM_CP_HELM ,"AM_CP_HELM" }, + {AM_BIOETHICS ,"AM_BIOETHICS" }, + {AM_BIOTECHNOLOGY ,"AM_BIOTECHNOLOGY" }, + {AM_CREATECREATURE ,"AM_CREATECREATURE" }, + {AM_CULTIVATION ,"AM_CULTIVATION" }, + {AM_FLAMECONTROL ,"AM_FLAMECONTROL" }, + {AM_CALLHOMUN ,"AM_CALLHOMUN" }, + {AM_REST ,"AM_REST" }, + {AM_DRILLMASTER ,"AM_DRILLMASTER" }, + {AM_HEALHOMUN ,"AM_HEALHOMUN" }, + {AM_RESURRECTHOMUN ,"AM_RESURRECTHOMUN" }, + + //Crusader class + {CR_TRUST ,"CR_TRUST" }, + {CR_AUTOGUARD ,"CR_AUTOGUARD" }, + {CR_SHIELDCHARGE ,"CR_SHIELDCHARGE" }, + {CR_SHIELDBOOMERANG ,"CR_SHIELDBOOMERANG" }, + {CR_REFLECTSHIELD ,"CR_REFLECTSHIELD" }, + {CR_HOLYCROSS ,"CR_HOLYCROSS" }, + {CR_GRANDCROSS ,"CR_GRANDCROSS" }, + {CR_DEVOTION ,"CR_DEVOTION" }, + {CR_PROVIDENCE ,"CR_PROVIDENCE" }, + {CR_DEFENDER ,"CR_DEFENDER" }, + {CR_SPEARQUICKEN ,"CR_SPEARQUICKEN" }, + + //Monk class + {MO_IRONHAND ,"MO_IRONHAND" }, + {MO_SPIRITSRECOVERY ,"MO_SPIRITSRECOVERY" }, + {MO_CALLSPIRITS ,"MO_CALLSPIRITS" }, + {MO_ABSORBSPIRITS ,"MO_ABSORBSPIRITS" }, + {MO_TRIPLEATTACK ,"MO_TRIPLEATTACK" }, + {MO_BODYRELOCATION ,"MO_BODYRELOCATION" }, + {MO_DODGE ,"MO_DODGE" }, + {MO_INVESTIGATE ,"MO_INVESTIGATE" }, + {MO_FINGEROFFENSIVE ,"MO_FINGEROFFENSIVE" }, + {MO_STEELBODY ,"MO_STEELBODY" }, + {MO_BLADESTOP ,"MO_BLADESTOP" }, + {MO_EXPLOSIONSPIRITS ,"MO_EXPLOSIONSPIRITS" }, + {MO_EXTREMITYFIST ,"MO_EXTREMITYFIST" }, + {MO_CHAINCOMBO ,"MO_CHAINCOMBO" }, + {MO_COMBOFINISH ,"MO_COMBOFINISH" }, + + //Sage class + {SA_ADVANCEDBOOK ,"SA_ADVANCEDBOOK" }, + {SA_CASTCANCEL ,"SA_CASTCANCEL" }, + {SA_MAGICROD ,"SA_MAGICROD" }, + {SA_SPELLBREAKER ,"SA_SPELLBREAKER" }, + {SA_FREECAST ,"SA_FREECAST" }, + {SA_AUTOSPELL ,"SA_AUTOSPELL" }, + {SA_FLAMELAUNCHER ,"SA_FLAMELAUNCHER" }, + {SA_FROSTWEAPON ,"SA_FROSTWEAPON" }, + {SA_LIGHTNINGLOADER ,"SA_LIGHTNINGLOADER" }, + {SA_SEISMICWEAPON ,"SA_SEISMICWEAPON" }, + {SA_DRAGONOLOGY ,"SA_DRAGONOLOGY" }, + {SA_VOLCANO ,"SA_VOLCANO" }, + {SA_DELUGE ,"SA_DELUGE" }, + {SA_VIOLENTGALE ,"SA_VIOLENTGALE" }, + {SA_LANDPROTECTOR ,"SA_LANDPROTECTOR" }, + {SA_DISPELL ,"SA_DISPELL" }, + {SA_ABRACADABRA ,"SA_ABRACADABRA" }, + {SA_MONOCELL ,"SA_MONOCELL" }, + {SA_CLASSCHANGE ,"SA_CLASSCHANGE" }, + {SA_SUMMONMONSTER ,"SA_SUMMONMONSTER" }, + {SA_REVERSEORCISH ,"SA_REVERSEORCISH" }, + {SA_DEATH ,"SA_DEATH" }, + {SA_FORTUNE ,"SA_FORTUNE" }, + {SA_TAMINGMONSTER ,"SA_TAMINGMONSTER" }, + {SA_QUESTION ,"SA_QUESTION" }, + {SA_GRAVITY ,"SA_GRAVITY" }, + {SA_LEVELUP ,"SA_LEVELUP" }, + {SA_INSTANTDEATH ,"SA_INSTANTDEATH" }, + {SA_FULLRECOVERY ,"SA_FULLRECOVERY" }, + {SA_COMA ,"SA_COMA" }, + + //Bard/Dancer skills + {BD_ADAPTATION ,"BD_ADAPTATION" }, + {BD_ENCORE ,"BD_ENCORE" }, + {BD_LULLABY ,"BD_LULLABY" }, + {BD_RICHMANKIM ,"BD_RICHMANKIM" }, + {BD_ETERNALCHAOS ,"BD_ETERNALCHAOS" }, + {BD_DRUMBATTLEFIELD ,"BD_DRUMBATTLEFIELD" }, + {BD_RINGNIBELUNGEN ,"BD_RINGNIBELUNGEN" }, + {BD_ROKISWEIL ,"BD_ROKISWEIL" }, + {BD_INTOABYSS ,"BD_INTOABYSS" }, + {BD_SIEGFRIED ,"BD_SIEGFRIED" }, + {BD_RAGNAROK ,"BD_RAGNAROK" }, + + //Bard class only skills + {BA_MUSICALLESSON ,"BA_MUSICALLESSON" }, + {BA_MUSICALSTRIKE ,"BA_MUSICALSTRIKE" }, + {BA_DISSONANCE ,"BA_DISSONANCE" }, + {BA_FROSTJOKER ,"BA_FROSTJOKER" }, + {BA_WHISTLE ,"BA_WHISTLE" }, + {BA_ASSASSINCROSS ,"BA_ASSASSINCROSS" }, + {BA_POEMBRAGI ,"BA_POEMBRAGI" }, + {BA_APPLEIDUN ,"BA_APPLEIDUN" }, + + //Dancer class only skills + {DC_DANCINGLESSON ,"DC_DANCINGLESSON" }, + {DC_THROWARROW ,"DC_THROWARROW" }, + {DC_UGLYDANCE ,"DC_UGLYDANCE" }, + {DC_SCREAM ,"DC_SCREAM" }, + {DC_HUMMING ,"DC_HUMMING" }, + {DC_DONTFORGETME ,"DC_DONTFORGETME" }, + {DC_FORTUNEKISS ,"DC_FORTUNEKISS" }, + {DC_SERVICEFORYOU ,"DC_SERVICEFORYOU" }, + + //Additional npc skills + {NPC_RANDOMMOVE ,"NPC_RANDOMMOVE" }, + {NPC_SPEEDUP ,"NPC_SPEEDUP" }, + {NPC_REVENGE ,"NPC_REVENGE" }, + + //Wedding skills + {WE_MALE ,"WE_MALE" }, + {WE_FEMALE ,"WE_FEMALE" }, + {WE_CALLPARTNER ,"WE_CALLPARTNER" }, + + //Item-Based skills + {ITM_TOMAHAWK ,"ITM_TOMAHAWK" }, + + //Additional npc skills + {NPC_DARKCROSS ,"NPC_DARKCROSS" }, + {NPC_GRANDDARKNESS ,"NPC_GRANDDARKNESS" }, + {NPC_DARKSTRIKE ,"NPC_DARKSTRIKE" }, + {NPC_DARKTHUNDER ,"NPC_DARKTHUNDER" }, + {NPC_STOP ,"NPC_STOP" }, + {NPC_WEAPONBRAKER ,"NPC_WEAPONBRAKER" }, + {NPC_ARMORBRAKE ,"NPC_ARMORBRAKE" }, + {NPC_HELMBRAKE ,"NPC_HELMBRAKE" }, + {NPC_SHIELDBRAKE ,"NPC_SHIELDBRAKE" }, + {NPC_UNDEADATTACK ,"NPC_UNDEADATTACK" }, + {NPC_CHANGEUNDEAD ,"NPC_CHANGEUNDEAD" }, + {NPC_POWERUP ,"NPC_POWERUP" }, + {NPC_AGIUP ,"NPC_AGIUP" }, + {NPC_SIEGEMODE ,"NPC_SIEGEMODE" }, + {NPC_CALLSLAVE ,"NPC_CALLSLAVE" }, + {NPC_INVISIBLE ,"NPC_INVISIBLE" }, + {NPC_RUN ,"NPC_RUN" }, + + //3rd skills + {LK_AURABLADE ,"LK_AURABLADE" }, + {LK_PARRYING ,"LK_PARRYING" }, + {LK_CONCENTRATION ,"LK_CONCENTRATION" }, + {LK_TENSIONRELAX ,"LK_TENSIONRELAX" }, + {LK_BERSERK ,"LK_BERSERK" }, + {LK_FURY ,"LK_FURY" }, + {HP_ASSUMPTIO ,"HP_ASSUMPTIO" }, + {HP_BASILICA ,"HP_BASILICA" }, + {HP_MEDITATIO ,"HP_MEDITATIO" }, + {HW_SOULDRAIN ,"HW_SOULDRAIN" }, + {HW_MAGICCRASHER ,"HW_MAGICCRASHER" }, + {HW_MAGICPOWER ,"HW_MAGICPOWER" }, + {PA_PRESSURE ,"PA_PRESSURE" }, + {PA_SACRIFICE ,"PA_SACRIFICE" }, + {PA_GOSPEL ,"PA_GOSPEL" }, + {CH_PALMSTRIKE ,"CH_PALMSTRIKE" }, + {CH_TIGERFIST ,"CH_TIGERFIST" }, + {CH_CHAINCRUSH ,"CH_CHAINCRUSH" }, + {PF_HPCONVERSION ,"PF_HPCONVERSION" }, + {PF_SOULCHANGE ,"PF_SOULCHANGE" }, + {PF_SOULBURN ,"PF_SOULBURN" }, + {ASC_KATAR ,"ASC_KATAR" }, + {ASC_HALLUCINATION ,"ASC_HALLUCINATION" }, + {ASC_EDP ,"ASC_EDP" }, + {ASC_BREAKER ,"ASC_BREAKER" }, + {SN_SIGHT ,"SN_SIGHT" }, + {SN_FALCONASSAULT ,"SN_FALCONASSAULT" }, + {SN_SHARPSHOOTING ,"SN_SHARPSHOOTING" }, + {SN_WINDWALK ,"SN_WINDWALK" }, + {WS_MELTDOWN ,"WS_MELTDOWN" }, + {WS_CREATECOIN ,"WS_CREATECOIN" }, + {WS_CREATENUGGET ,"WS_CREATENUGGET" }, + {WS_CARTBOOST ,"WS_CARTBOOST" }, + {WS_SYSTEMCREATE ,"WS_SYSTEMCREATE" }, + {ST_CHASEWALK ,"ST_CHASEWALK" }, + {ST_REJECTSWORD ,"ST_REJECTSWORD" }, + {ST_STEALBACKPACK ,"ST_STEALBACKPACK" }, + {CR_ALCHEMY ,"CR_ALCHEMY" }, + {CR_SYNTHESISPOTION ,"CR_SYNTHESISPOTION" }, + {CG_ARROWVULCAN ,"CG_ARROWVULCAN" }, + {CG_MOONLIT ,"CG_MOONLIT" }, + {CG_MARIONETTE ,"CG_MARIONETTE" }, + {LK_SPIRALPIERCE ,"LK_SPIRALPIERCE" }, + {LK_HEADCRUSH ,"LK_HEADCRUSH" }, + {LK_JOINTBEAT ,"LK_JOINTBEAT" }, + {HW_NAPALMVULCAN ,"HW_NAPALMVULCAN" }, + {CH_SOULCOLLECT ,"CH_SOULCOLLECT" }, + {PF_MINDBREAKER ,"PF_MINDBREAKER" }, + {PF_MEMORIZE ,"PF_MEMORIZE" }, + {PF_FOGWALL ,"PF_FOGWALL" }, + {PF_SPIDERWEB ,"PF_SPIDERWEB" }, + {ASC_METEORASSAULT ,"ASC_METEORASSAULT" }, + {ASC_CDP ,"ASC_CDP" }, + {WE_BABY ,"WE_BABY" }, + {WE_CALLPARENT ,"WE_CALLPARENT" }, + {WE_CALLBABY ,"WE_CALLBABY" }, + + //Taekwon class + {TK_RUN ,"TK_RUN" }, + {TK_READYSTORM ,"TK_READYSTORM" }, + {TK_STORMKICK ,"TK_STORMKICK" }, + {TK_READYDOWN ,"TK_READYDOWN" }, + {TK_DOWNKICK ,"TK_DOWNKICK" }, + {TK_READYTURN ,"TK_READYTURN" }, + {TK_TURNKICK ,"TK_TURNKICK" }, + {TK_READYCOUNTER ,"TK_READYCOUNTER" }, + {TK_COUNTER ,"TK_COUNTER" }, + {TK_DODGE ,"TK_DODGE" }, + {TK_JUMPKICK ,"TK_JUMPKICK" }, + {TK_HPTIME ,"TK_HPTIME" }, + {TK_SPTIME ,"TK_SPTIME" }, + {TK_POWER ,"TK_POWER" }, + {TK_SEVENWIND ,"TK_SEVENWIND" }, + {TK_HIGHJUMP ,"TK_HIGHJUMP" }, + + //Star Gladiator class + {SG_FEEL ,"SG_FEEL" }, + {SG_SUN_WARM ,"SG_SUN_WARM" }, + {SG_MOON_WARM ,"SG_MOON_WARM" }, + {SG_STAR_WARM ,"SG_STAR_WARM" }, + {SG_SUN_COMFORT ,"SG_SUN_COMFORT" }, + {SG_MOON_COMFORT ,"SG_MOON_COMFORT" }, + {SG_STAR_COMFORT ,"SG_STAR_COMFORT" }, + {SG_HATE ,"SG_HATE" }, + {SG_SUN_ANGER ,"SG_SUN_ANGER" }, + {SG_MOON_ANGER ,"SG_MOON_ANGER" }, + {SG_STAR_ANGER ,"SG_STAR_ANGER" }, + {SG_SUN_BLESS ,"SG_SUN_BLESS" }, + {SG_MOON_BLESS ,"SG_MOON_BLESS" }, + {SG_STAR_BLESS ,"SG_STAR_BLESS" }, + {SG_DEVIL ,"SG_DEVIL" }, + {SG_FRIEND ,"SG_FRIEND" }, + {SG_KNOWLEDGE ,"SG_KNOWLEDGE" }, + {SG_FUSION ,"SG_FUSION" }, + + //Soullinker class skills + their spirit skills + {SL_ALCHEMIST ,"SL_ALCHEMIST" }, + {AM_BERSERKPITCHER ,"AM_BERSERKPITCHER" }, + {SL_MONK ,"SL_MONK" }, + {SL_STAR ,"SL_STAR" }, + {SL_SAGE ,"SL_SAGE" }, + {SL_CRUSADER ,"SL_CRUSADER" }, + {SL_SUPERNOVICE ,"SL_SUPERNOVICE" }, + {SL_KNIGHT ,"SL_KNIGHT" }, + {SL_WIZARD ,"SL_WIZARD" }, + {SL_PRIEST ,"SL_PRIEST" }, + {SL_BARDDANCER ,"SL_BARDDANCER" }, + {SL_ROGUE ,"SL_ROGUE" }, + {SL_ASSASIN ,"SL_ASSASIN" }, + {SL_BLACKSMITH ,"SL_BLACKSMITH" }, + {BS_ADRENALINE2 ,"BS_ADRENALINE2" }, + {SL_HUNTER ,"SL_HUNTER" }, + {SL_SOULLINKER ,"SL_SOULLINKER" }, + {SL_KAIZEL ,"SL_KAIZEL" }, + {SL_KAAHI ,"SL_KAAHI" }, + {SL_KAUPE ,"SL_KAUPE" }, + {SL_KAITE ,"SL_KAITE" }, + {SL_KAINA ,"SL_KAINA" }, + {SL_STIN ,"SL_STIN" }, + {SL_STUN ,"SL_STUN" }, + {SL_SMA ,"SL_SMA" }, + {SL_SWOO ,"SL_SWOO" }, + {SL_SKE ,"SL_SKE" }, + {SL_SKA ,"SL_SKA" }, + + //Seams like quest skills for 3rd classes + {SM_SELFPROVOKE ,"SM_SELFPROVOKE" }, + { NPC_EMOTION_ON ,"NPC_EMOTION_ON" }, + { ST_PRESERVE ,"ST_PRESERVE" }, + { ST_FULLSTRIP ,"ST_FULLSTRIP" }, + { WS_WEAPONREFINE ,"WS_WEAPONREFINE" }, + { CR_SLIMPITCHER ,"CR_SLIMPITCHER" }, + { CR_FULLPROTECTION ,"CR_FULLPROTECTION" }, + { PA_SHIELDCHAIN ,"PA_SHIELDCHAIN" }, + { HP_MANARECHARGE ,"HP_MANARECHARGE" }, + { PF_DOUBLECASTING ,"PF_DOUBLECASTING" }, + { HW_GANBANTEIN ,"HW_GANBANTEIN" }, + { HW_GRAVITATION ,"HW_GRAVITATION" }, + { WS_CARTTERMINATION ,"WS_CARTTERMINATION" }, + { WS_OVERTHRUSTMAX ,"WS_OVERTHRUSTMAX" }, + { CG_LONGINGFREEDOM ,"CG_LONGINGFREEDOM" }, + { CG_HERMODE ,"CG_HERMODE" }, + { CG_TAROTCARD ,"CG_TAROTCARD" }, + { CR_ACIDDEMONSTRATION ,"CR_ACIDDEMONSTRATION" }, + { CR_CULTIVATION ,"CR_CULTIVATION" },// Removed on kRO (renewal)}, + { ITEM_ENCHANTARMS ,"ITEM_ENCHANTARMS" }, + { TK_MISSION ,"TK_MISSION" }, + { SL_HIGH ,"SL_HIGH" }, + { KN_ONEHAND ,"KN_ONEHAND" }, + { AM_TWILIGHT1 ,"AM_TWILIGHT1" }, + { AM_TWILIGHT2 ,"AM_TWILIGHT2" }, + { AM_TWILIGHT3 ,"AM_TWILIGHT3" }, + { HT_POWER ,"HT_POWER" }, + + //Gunslinger class + {GS_GLITTERING ,"GS_GLITTERING" }, + {GS_FLING ,"GS_FLING" }, + {GS_TRIPLEACTION ,"GS_TRIPLEACTION" }, + {GS_BULLSEYE ,"GS_BULLSEYE" }, + {GS_MADNESSCANCEL ,"GS_MADNESSCANCEL" }, + {GS_ADJUSTMENT ,"GS_ADJUSTMENT" }, + {GS_INCREASING ,"GS_INCREASING" }, + {GS_MAGICALBULLET ,"GS_MAGICALBULLET" }, + {GS_CRACKER ,"GS_CRACKER" }, + {GS_SINGLEACTION ,"GS_SINGLEACTION" }, + {GS_SNAKEEYE ,"GS_SNAKEEYE" }, + {GS_CHAINACTION ,"GS_CHAINACTION" }, + {GS_TRACKING ,"GS_TRACKING" }, + {GS_DISARM ,"GS_DISARM" }, + {GS_PIERCINGSHOT ,"GS_PIERCINGSHOT" }, + {GS_RAPIDSHOWER ,"GS_RAPIDSHOWER" }, + {GS_DESPERADO ,"GS_DESPERADO" }, + {GS_GATLINGFEVER ,"GS_GATLINGFEVER" }, + {GS_DUST ,"GS_DUST" }, + {GS_FULLBUSTER ,"GS_FULLBUSTER" }, + {GS_SPREADATTACK ,"GS_SPREADATTACK" }, + {GS_GROUNDDRIFT ,"GS_GROUNDDRIFT" }, + + //Ninja class + {NJ_TOBIDOUGU ,"NJ_TOBIDOUGU" }, + {NJ_SYURIKEN ,"NJ_SYURIKEN" }, + {NJ_KUNAI ,"NJ_KUNAI" }, + {NJ_HUUMA ,"NJ_HUUMA" }, + {NJ_ZENYNAGE ,"NJ_ZENYNAGE" }, + {NJ_TATAMIGAESHI ,"NJ_TATAMIGAESHI" }, + {NJ_KASUMIKIRI ,"NJ_KASUMIKIRI" }, + {NJ_SHADOWJUMP ,"NJ_SHADOWJUMP" }, + {NJ_KIRIKAGE ,"NJ_KIRIKAGE" }, + {NJ_UTSUSEMI ,"NJ_UTSUSEMI" }, + {NJ_BUNSINJYUTSU ,"NJ_BUNSINJYUTSU" }, + {NJ_NINPOU ,"NJ_NINPOU" }, + {NJ_KOUENKA ,"NJ_KOUENKA" }, + {NJ_KAENSIN ,"NJ_KAENSIN" }, + {NJ_BAKUENRYU ,"NJ_BAKUENRYU" }, + {NJ_HYOUSENSOU ,"NJ_HYOUSENSOU" }, + {NJ_SUITON ,"NJ_SUITON" }, + {NJ_HYOUSYOURAKU ,"NJ_HYOUSYOURAKU" }, + {NJ_HUUJIN ,"NJ_HUUJIN" }, + {NJ_RAIGEKISAI ,"NJ_RAIGEKISAI" }, + {NJ_KAMAITACHI ,"NJ_KAMAITACHI" }, + {NJ_NEN ,"NJ_NEN" }, + {NJ_ISSEN ,"NJ_ISSEN" }, + + // Misc/Mob skills + {MB_FIGHTING ,"MB_FIGHTING" }, + {MB_NEUTRAL ,"MB_NEUTRAL" }, + {MB_TAIMING_PUTI ,"MB_TAIMING_PUTI" }, + {MB_WHITEPOTION ,"MB_WHITEPOTION" }, + {MB_MENTAL ,"MB_MENTAL" }, + {MB_CARDPITCHER ,"MB_CARDPITCHER" }, + {MB_PETPITCHER ,"MB_PETPITCHER" }, + {MB_BODYSTUDY ,"MB_BODYSTUDY" }, + {MB_BODYALTER ,"MB_BODYALTER" }, + {MB_PETMEMORY ,"MB_PETMEMORY" }, + {MB_M_TELEPORT ,"MB_M_TELEPORT" }, + {MB_B_GAIN ,"MB_B_GAIN" }, + {MB_M_GAIN ,"MB_M_GAIN" }, + {MB_MISSION ,"MB_MISSION" }, + {MB_MUNAKKNOWLEDGE ,"MB_MUNAKKNOWLEDGE" }, + {MB_MUNAKBALL ,"MB_MUNAKBALL" }, + {MB_SCROLL ,"MB_SCROLL" }, + {MB_B_GATHERING ,"MB_B_GATHERING" }, + {MB_M_GATHERING ,"MB_M_GATHERING" }, + {MB_B_EXCLUDE ,"MB_B_EXCLUDE" }, + {MB_B_DRIFT ,"MB_B_DRIFT" }, + {MB_B_WALLRUSH ,"MB_B_WALLRUSH" }, + {MB_M_WALLRUSH ,"MB_M_WALLRUSH" }, + {MB_B_WALLSHIFT ,"MB_B_WALLSHIFT" }, + {MB_M_WALLCRASH ,"MB_M_WALLCRASH" }, + {MB_M_REINCARNATION ,"MB_M_REINCARNATION" }, + {MB_B_EQUIP ,"MB_B_EQUIP" }, + + //additional Soul Linker skills + {SL_DEATHKNIGHT ,"SL_DEATHKNIGHT" }, + {SL_COLLECTOR ,"SL_COLLECTOR" }, + {SL_NINJA ,"SL_NINJA" }, + {SL_GUNNER ,"SL_GUNNER" }, + {AM_TWILIGHT4 ,"AM_TWILIGHT4" }, + {DA_RESET ,"DA_RESET" }, + {DE_BERSERKAIZER ,"DE_BERSERKAIZER" }, + {DA_DARKPOWER ,"DA_DARKPOWER" }, + + // Misc/Mob skills + {DE_PASSIVE ,"DE_PASSIVE" }, + {DE_PATTACK ,"DE_PATTACK" }, + {DE_PSPEED ,"DE_PSPEED" }, + {DE_PDEFENSE ,"DE_PDEFENSE" }, + {DE_PCRITICAL ,"DE_PCRITICAL" }, + {DE_PHP ,"DE_PHP" }, + {DE_PSP ,"DE_PSP" }, + {DE_RESET ,"DE_RESET" }, + {DE_RANKING ,"DE_RANKING" }, + {DE_PTRIPLE ,"DE_PTRIPLE" }, + {DE_ENERGY ,"DE_ENERGY" }, + {DE_NIGHTMARE ,"DE_NIGHTMARE" }, + {DE_SLASH ,"DE_SLASH" }, + {DE_COIL ,"DE_COIL" }, + {DE_WAVE ,"DE_WAVE" }, + {DE_REBIRTH ,"DE_REBIRTH" }, + {DE_AURA ,"DE_AURA" }, + {DE_FREEZER ,"DE_FREEZER" }, + {DE_CHANGEATTACK ,"DE_CHANGEATTACK" }, + {DE_PUNISH ,"DE_PUNISH" }, + {DE_POISON ,"DE_POISON" }, + {DE_INSTANT ,"DE_INSTANT" }, + {DE_WARNING ,"DE_WARNING" }, + {DE_RANKEDKNIFE ,"DE_RANKEDKNIFE" }, + {DE_RANKEDGRADIUS ,"DE_RANKEDGRADIUS" }, + {DE_GAUGE ,"DE_GAUGE" }, + {DE_GTIME ,"DE_GTIME" }, + {DE_GPAIN ,"DE_GPAIN" }, + {DE_GSKILL ,"DE_GSKILL" }, + {DE_GKILL ,"DE_GKILL" }, + {DE_ACCEL ,"DE_ACCEL" }, + {DE_BLOCKDOUBLE ,"DE_BLOCKDOUBLE" }, + {DE_BLOCKMELEE ,"DE_BLOCKMELEE" }, + {DE_BLOCKFAR ,"DE_BLOCKFAR" }, + {DE_FRONTATTACK ,"DE_FRONTATTACK" }, + {DE_DANGERATTACK ,"DE_DANGERATTACK" }, + {DE_TWINATTACK ,"DE_TWINATTACK" }, + {DE_WINDATTACK ,"DE_WINDATTACK" }, + {DE_WATERATTACK ,"DE_WATERATTACK" }, + + {DA_ENERGY ,"DA_ENERGY" }, + {DA_CLOUD ,"DA_CLOUD" }, + {DA_FIRSTSLOT ,"DA_FIRSTSLOT" }, + {DA_HEADDEF ,"DA_HEADDEF" }, + {DA_SPACE ,"DA_SPACE" }, + {DA_TRANSFORM ,"DA_TRANSFORM" }, + {DA_EXPLOSION ,"DA_EXPLOSION" }, + {DA_REWARD ,"DA_REWARD" }, + {DA_CRUSH ,"DA_CRUSH" }, + {DA_ITEMREBUILD ,"DA_ITEMREBUILD" }, + {DA_ILLUSION ,"DA_ILLUSION" }, + {DA_NUETRALIZE ,"DA_NUETRALIZE" }, + {DA_RUNNER ,"DA_RUNNER" }, + {DA_TRANSFER ,"DA_TRANSFER" }, + {DA_WALL ,"DA_WALL" }, + {DA_ZENY ,"DA_ZENY" }, + {DA_REVENGE ,"DA_REVENGE" }, + {DA_EARPLUG ,"DA_EARPLUG" }, + {DA_CONTRACT ,"DA_CONTRACT" }, + {DA_BLACK ,"DA_BLACK" }, + {DA_DREAM ,"DA_DREAM" }, + {DA_MAGICCART ,"DA_MAGICCART" }, + {DA_COPY ,"DA_COPY" }, + {DA_CRYSTAL ,"DA_CRYSTAL" }, + {DA_EXP ,"DA_EXP" }, + {DA_CARTSWING ,"DA_CARTSWING" }, + {DA_REBUILD ,"DA_REBUILD" }, + {DA_JOBCHANGE ,"DA_JOBCHANGE" }, + {DA_EDARKNESS ,"DA_EDARKNESS" }, + {DA_EGUARDIAN ,"DA_EGUARDIAN" }, + {DA_TIMEOUT ,"DA_TIMEOUT" }, + {ALL_TIMEIN ,"ALL_TIMEIN" }, + {DA_ZENYRANK ,"DA_ZENYRANK" }, + {DA_ACCESSORYMIX ,"DA_ACCESSORYMIX" }, + + // Misc/Mob skills + {NPC_EARTHQUAKE ,"NPC_EARTHQUAKE" }, + {NPC_FIREBREATH ,"NPC_FIREBREATH" }, + {NPC_ICEBREATH ,"NPC_ICEBREATH" }, + {NPC_THUNDERBREATH ,"NPC_THUNDERBREATH" }, + {NPC_ACIDBREATH ,"NPC_ACIDBREATH" }, + {NPC_DARKNESSBREATH ,"NPC_DARKNESSBREATH" }, + {NPC_DRAGONFEAR ,"NPC_DRAGONFEAR" }, + {NPC_BLEEDING ,"NPC_BLEEDING" }, + {NPC_PULSESTRIKE ,"NPC_PULSESTRIKE" }, + {NPC_HELLJUDGEMENT ,"NPC_HELLJUDGEMENT" }, + {NPC_WIDESILENCE ,"NPC_WIDESILENCE" }, + {NPC_WIDEFREEZE ,"NPC_WIDEFREEZE" }, + {NPC_WIDEBLEEDING ,"NPC_WIDEBLEEDING" }, + {NPC_WIDESTONE ,"NPC_WIDESTONE" }, + {NPC_WIDECONFUSE ,"NPC_WIDECONFUSE" }, + {NPC_WIDESLEEP ,"NPC_WIDESLEEP" }, + {NPC_WIDESIGHT ,"NPC_WIDESIGHT" }, + {NPC_EVILLAND ,"NPC_EVILLAND" }, + {NPC_MAGICMIRROR ,"NPC_MAGICMIRROR" }, + {NPC_SLOWCAST ,"NPC_SLOWCAST" }, + {NPC_CRITICALWOUND ,"NPC_CRITICALWOUND" }, + {NPC_EXPULSION ,"NPC_EXPULSION" }, + {NPC_STONESKIN ,"NPC_STONESKIN" }, + {NPC_ANTIMAGIC ,"NPC_ANTIMAGIC" }, + {NPC_WIDECURSE ,"NPC_WIDECURSE" }, + {NPC_WIDESTUN ,"NPC_WIDESTUN" }, + {NPC_VAMPIRE_GIFT ,"NPC_VAMPIRE_GIFT" }, + {NPC_WIDESOULDRAIN ,"NPC_WIDESOULDRAIN" }, + + {ALL_INCCARRY ,"ALL_INCCARRY" }, + {NPC_TALK ,"NPC_TALK" }, + {NPC_HELLPOWER ,"NPC_HELLPOWER" }, + {NPC_WIDEHELLDIGNITY ,"NPC_WIDEHELLDIGNITY" }, + {NPC_INVINCIBLE ,"NPC_INVINCIBLE" }, + {NPC_INVINCIBLEOFF ,"NPC_INVINCIBLEOFF" }, + {NPC_ALLHEAL ,"NPC_ALLHEAL" }, + {GM_SANDMAN ,"GM_SANDMAN" }, + {CASH_BLESSING ,"CASH_BLESSING" }, + {CASH_INCAGI ,"CASH_INCAGI" }, + {CASH_ASSUMPTIO ,"CASH_ASSUMPTIO" }, + {ALL_CATCRY ,"ALL_CATCRY" }, + {ALL_PARTYFLEE ,"ALL_PARTYFLEE" }, + {ALL_ANGEL_PROTECT ,"ALL_ANGEL_PROTECT" }, + {ALL_DREAM_SUMMERNIGHT ,"ALL_DREAM_SUMMERNIGHT" }, + {NPC_CHANGEUNDEAD2 ,"NPC_CHANGEUNDEAD2" }, + {ALL_REVERSEORCISH ,"ALL_REVERSEORCISH" }, + {ALL_WEWISH ,"ALL_WEWISH" }, + {ALL_SONKRAN ,"ALL_SONKRAN" }, + {NPC_WIDEHEALTHFEAR ,"NPC_WIDEHEALTHFEAR" }, + {NPC_WIDEBODYBURNNING ,"NPC_WIDEBODYBURNNING" }, + {NPC_WIDEFROSTMISTY ,"NPC_WIDEFROSTMISTY" }, + {NPC_WIDECOLD ,"NPC_WIDECOLD" }, + {NPC_WIDE_DEEP_SLEEP ,"NPC_WIDE_DEEP_SLEEP" }, + {NPC_WIDESIREN ,"NPC_WIDESIREN" }, + {NPC_VENOMFOG ,"NPC_VENOMFOG" }, + {NPC_MILLENNIUMSHIELD ,"NPC_MILLENNIUMSHIELD" }, + {NPC_COMET ,"NPC_COMET" }, + {NPC_ICEMINE ,"NPC_ICEMINE" }, + {NPC_ICEEXPLO ,"NPC_ICEEXPLO" }, + {NPC_FLAMECROSS ,"NPC_FLAMECROSS" }, + {NPC_PULSESTRIKE2 ,"NPC_PULSESTRIKE2" }, + {NPC_DANCINGBLADE ,"NPC_DANCINGBLADE" }, + {NPC_DANCINGBLADE_ATK ,"NPC_DANCINGBLADE_ATK" }, + {NPC_DARKPIERCING ,"NPC_DARKPIERCING" }, + {NPC_MAXPAIN ,"NPC_MAXPAIN" }, + {NPC_MAXPAIN_ATK ,"NPC_MAXPAIN_ATK" }, + {NPC_DEATHSUMMON ,"NPC_DEATHSUMMON" }, + {NPC_HELLBURNING ,"NPC_HELLBURNING" }, + {NPC_JACKFROST ,"NPC_JACKFROST" }, + {NPC_WIDEWEB ,"NPC_WIDEWEB" }, + {NPC_WIDESUCK ,"NPC_WIDESUCK" }, + {NPC_STORMGUST2 ,"NPC_STORMGUST2" }, + {NPC_FIRESTORM ,"NPC_FIRESTORM" }, + {NPC_REVERBERATION ,"NPC_REVERBERATION" }, + {NPC_REVERBERATION_ATK ,"NPC_REVERBERATION_ATK" }, + {NPC_LEX_AETERNA ,"NPC_LEX_AETERNA" }, + {NPC_ARROWSTORM ,"NPC_ARROWSTORM" }, + {NPC_CHEAL ,"NPC_CHEAL" }, + {NPC_SR_CURSEDCIRCLE ,"NPC_SR_CURSEDCIRCLE" }, + {NPC_DRAGONBREATH ,"NPC_DRAGONBREATH" }, + {NPC_FATALMENACE ,"NPC_FATALMENACE" }, + {NPC_MAGMA_ERUPTION ,"NPC_MAGMA_ERUPTION" }, + {NPC_MAGMA_ERUPTION_DOTDAMAGE ,"NPC_MAGMA_ERUPTION_DOTDAMAGE" }, + {NPC_MANDRAGORA ,"NPC_MANDRAGORA" }, + {NPC_PSYCHIC_WAVE ,"NPC_PSYCHIC_WAVE" }, + {NPC_RAYOFGENESIS ,"NPC_RAYOFGENESIS" }, + {NPC_VENOMIMPRESS ,"NPC_VENOMIMPRESS" }, + {NPC_CLOUD_KILL ,"NPC_CLOUD_KILL" }, + {NPC_IGNITIONBREAK ,"NPC_IGNITIONBREAK" }, + {NPC_PHANTOMTHRUST ,"NPC_PHANTOMTHRUST" }, + {NPC_POISON_BUSTER ,"NPC_POISON_BUSTER" }, + {NPC_HALLUCINATIONWALK ,"NPC_HALLUCINATIONWALK" }, + {NPC_ELECTRICWALK ,"NPC_ELECTRICWALK" }, + {NPC_FIREWALK ,"NPC_FIREWALK" }, + {NPC_WIDEDISPEL ,"NPC_WIDEDISPEL" }, + {NPC_LEASH ,"NPC_LEASH" }, + {NPC_WIDELEASH ,"NPC_WIDELEASH" }, + {NPC_WIDECRITICALWOUND ,"NPC_WIDECRITICALWOUND" }, + {NPC_EARTHQUAKE_K ,"NPC_EARTHQUAKE_K" }, + {NPC_ALL_STAT_DOWN ,"NPC_ALL_STAT_DOWN" }, + {NPC_GRADUAL_GRAVITY ,"NPC_GRADUAL_GRAVITY" }, + {NPC_DAMAGE_HEAL ,"NPC_DAMAGE_HEAL" }, + {NPC_IMMUNE_PROPERTY ,"NPC_IMMUNE_PROPERTY" }, + {NPC_MOVE_COORDINATE ,"NPC_MOVE_COORDINATE" }, + {NPC_WIDEBLEEDING2 ,"NPC_WIDEBLEEDING2" }, + {NPC_WIDESILENCE2 ,"NPC_WIDESILENCE2" }, + {NPC_WIDESTUN2 ,"NPC_WIDESTUN2" }, + {NPC_WIDESTONE2 ,"NPC_WIDESTONE2" }, + {NPC_WIDESLEEP2 ,"NPC_WIDESLEEP2" }, + {NPC_WIDECURSE2 ,"NPC_WIDECURSE2" }, + {NPC_WIDECONFUSE2 ,"NPC_WIDECONFUSE2" }, + {NPC_WIDEFREEZE2 ,"NPC_WIDEFREEZE2" }, + {NPC_BLEEDING2 ,"NPC_BLEEDING2" }, + {NPC_ICEBREATH2 ,"NPC_ICEBREATH2" }, + {NPC_ACIDBREATH2 ,"NPC_ACIDBREATH2" }, + {NPC_EVILLAND2 ,"NPC_EVILLAND2" }, + {NPC_HELLJUDGEMENT2 ,"NPC_HELLJUDGEMENT2" }, + {NPC_RAINOFMETEOR ,"NPC_RAINOFMETEOR" }, + {NPC_GROUNDDRIVE ,"NPC_GROUNDDRIVE" }, + {NPC_RELIEVE_ON ,"NPC_RELIEVE_ON" }, + {NPC_RELIEVE_OFF ,"NPC_RELIEVE_OFF" }, + {NPC_LOCKON_LASER ,"NPC_LOCKON_LASER" }, + {NPC_LOCKON_LASER_ATK ,"NPC_LOCKON_LASER_ATK" }, + {NPC_SEEDTRAP ,"NPC_SEEDTRAP" }, + {NPC_DEADLYCURSE ,"NPC_DEADLYCURSE" }, + {NPC_RANDOMBREAK ,"NPC_RANDOMBREAK" }, + {NPC_STRIP_SHADOW ,"NPC_STRIP_SHADOW" }, + {NPC_DEADLYCURSE2 ,"NPC_DEADLYCURSE2" }, + {NPC_CANE_OF_EVIL_EYE ,"NPC_CANE_OF_EVIL_EYE" }, + {NPC_CURSE_OF_RED_CUBE ,"NPC_CURSE_OF_RED_CUBE" }, + {NPC_CURSE_OF_BLUE_CUBE ,"NPC_CURSE_OF_BLUE_CUBE" }, + {NPC_KILLING_AURA ,"NPC_KILLING_AURA" }, + + //additional Quest Skills + {KN_CHARGEATK ,"KN_CHARGEATK" }, + {CR_SHRINK ,"CR_SHRINK" }, + {AS_SONICACCEL ,"AS_SONICACCEL" }, + {AS_VENOMKNIFE ,"AS_VENOMKNIFE" }, + {RG_CLOSECONFINE ,"RG_CLOSECONFINE" }, + {WZ_SIGHTBLASTER ,"WZ_SIGHTBLASTER" }, + {SA_CREATECON ,"SA_CREATECON" }, + {SA_ELEMENTWATER ,"SA_ELEMENTWATER" }, + {HT_PHANTASMIC ,"HT_PHANTASMIC" }, + {BA_PANGVOICE ,"BA_PANGVOICE" }, + {DC_WINKCHARM ,"DC_WINKCHARM" }, + {BS_UNFAIRLYTRICK ,"BS_UNFAIRLYTRICK" }, + {BS_GREED ,"BS_GREED" }, + {PR_REDEMPTIO ,"PR_REDEMPTIO" }, + {MO_KITRANSLATION ,"MO_KITRANSLATION" }, + {MO_BALKYOUNG ,"MO_BALKYOUNG" }, + {SA_ELEMENTGROUND ,"SA_ELEMENTGROUND" }, + {SA_ELEMENTFIRE ,"SA_ELEMENTFIRE" }, + {SA_ELEMENTWIND ,"SA_ELEMENTWIND" }, + + //RuneKnight Skills + {RK_ENCHANTBLADE ,"RK_ENCHANTBLADE" }, + {RK_SONICWAVE ,"RK_SONICWAVE" }, + {RK_DEATHBOUND ,"RK_DEATHBOUND" }, + {RK_HUNDREDSPEAR ,"RK_HUNDREDSPEAR" }, + {RK_WINDCUTTER ,"RK_WINDCUTTER" }, + {RK_IGNITIONBREAK ,"RK_IGNITIONBREAK" }, + {RK_DRAGONTRAINING ,"RK_DRAGONTRAINING" }, + {RK_DRAGONBREATH ,"RK_DRAGONBREATH" }, + {RK_DRAGONHOWLING ,"RK_DRAGONHOWLING" }, + {RK_RUNEMASTERY ,"RK_RUNEMASTERY" }, + {RK_MILLENNIUMSHIELD ,"RK_MILLENNIUMSHIELD" }, + {RK_CRUSHSTRIKE ,"RK_CRUSHSTRIKE" }, + {RK_REFRESH ,"RK_REFRESH" }, + {RK_GIANTGROWTH ,"RK_GIANTGROWTH" }, + {RK_STONEHARDSKIN ,"RK_STONEHARDSKIN" }, + {RK_VITALITYACTIVATION ,"RK_VITALITYACTIVATION" }, + {RK_STORMBLAST ,"RK_STORMBLAST" }, + {RK_FIGHTINGSPIRIT ,"RK_FIGHTINGSPIRIT" }, + {RK_ABUNDANCE ,"RK_ABUNDANCE" }, + {RK_PHANTOMTHRUST ,"RK_PHANTOMTHRUST" }, + + //GrandCross skills + {GC_VENOMIMPRESS ,"GC_VENOMIMPRESS" }, + {GC_CROSSIMPACT ,"GC_CROSSIMPACT" }, + {GC_DARKILLUSION ,"GC_DARKILLUSION" }, + {GC_RESEARCHNEWPOISON ,"GC_RESEARCHNEWPOISON" }, + {GC_CREATENEWPOISON ,"GC_CREATENEWPOISON" }, + {GC_ANTIDOTE ,"GC_ANTIDOTE" }, + {GC_POISONINGWEAPON ,"GC_POISONINGWEAPON" }, + {GC_WEAPONBLOCKING ,"GC_WEAPONBLOCKING" }, + {GC_COUNTERSLASH ,"GC_COUNTERSLASH" }, + {GC_WEAPONCRUSH ,"GC_WEAPONCRUSH" }, + {GC_VENOMPRESSURE ,"GC_VENOMPRESSURE" }, + {GC_POISONSMOKE ,"GC_POISONSMOKE" }, + {GC_CLOAKINGEXCEED ,"GC_CLOAKINGEXCEED" }, + {GC_PHANTOMMENACE ,"GC_PHANTOMMENACE" }, + {GC_HALLUCINATIONWALK ,"GC_HALLUCINATIONWALK" }, + {GC_ROLLINGCUTTER ,"GC_ROLLINGCUTTER" }, + {GC_CROSSRIPPERSLASHER ,"GC_CROSSRIPPERSLASHER" }, + + //Arch Bishop skills + {AB_JUDEX ,"AB_JUDEX" }, + {AB_ANCILLA ,"AB_ANCILLA" }, + {AB_ADORAMUS ,"AB_ADORAMUS" }, + {AB_CLEMENTIA ,"AB_CLEMENTIA" }, + {AB_CANTO ,"AB_CANTO" }, + {AB_CHEAL ,"AB_CHEAL" }, + {AB_EPICLESIS ,"AB_EPICLESIS" }, + {AB_PRAEFATIO ,"AB_PRAEFATIO" }, + {AB_ORATIO ,"AB_ORATIO" }, + {AB_LAUDAAGNUS ,"AB_LAUDAAGNUS" }, + {AB_LAUDARAMUS ,"AB_LAUDARAMUS" }, + {AB_EUCHARISTICA ,"AB_EUCHARISTICA" },// Removed on kRO + {AB_RENOVATIO ,"AB_RENOVATIO" }, + {AB_HIGHNESSHEAL ,"AB_HIGHNESSHEAL" }, + {AB_CLEARANCE ,"AB_CLEARANCE" }, + {AB_EXPIATIO ,"AB_EXPIATIO" }, + {AB_DUPLELIGHT ,"AB_DUPLELIGHT" }, + {AB_DUPLELIGHT_MELEE ,"AB_DUPLELIGHT_MELEE" }, + {AB_DUPLELIGHT_MAGIC ,"AB_DUPLELIGHT_MAGIC" }, + {AB_SILENTIUM ,"AB_SILENTIUM" }, + + //Warlock skills + {WL_WHITEIMPRISON ,"WL_WHITEIMPRISON" }, + {WL_SOULEXPANSION ,"WL_SOULEXPANSION" }, + {WL_FROSTMISTY ,"WL_FROSTMISTY" }, + {WL_JACKFROST ,"WL_JACKFROST" }, + {WL_MARSHOFABYSS ,"WL_MARSHOFABYSS" }, + {WL_RECOGNIZEDSPELL ,"WL_RECOGNIZEDSPELL" }, + {WL_SIENNAEXECRATE ,"WL_SIENNAEXECRATE" }, + {WL_RADIUS ,"WL_RADIUS" }, + {WL_STASIS ,"WL_STASIS" }, + {WL_DRAINLIFE ,"WL_DRAINLIFE" }, + {WL_CRIMSONROCK ,"WL_CRIMSONROCK" }, + {WL_HELLINFERNO ,"WL_HELLINFERNO" }, + {WL_COMET ,"WL_COMET" }, + {WL_CHAINLIGHTNING ,"WL_CHAINLIGHTNING" }, + {WL_CHAINLIGHTNING_ATK ,"WL_CHAINLIGHTNING_ATK" }, + {WL_EARTHSTRAIN ,"WL_EARTHSTRAIN" }, + {WL_TETRAVORTEX ,"WL_TETRAVORTEX" }, + {WL_TETRAVORTEX_FIRE ,"WL_TETRAVORTEX_FIRE" }, + {WL_TETRAVORTEX_WATER ,"WL_TETRAVORTEX_WATER" }, + {WL_TETRAVORTEX_WIND ,"WL_TETRAVORTEX_WIND" }, + {WL_TETRAVORTEX_GROUND ,"WL_TETRAVORTEX_GROUND" }, + {WL_SUMMONFB ,"WL_SUMMONFB" }, + {WL_SUMMONBL ,"WL_SUMMONBL" }, + {WL_SUMMONWB ,"WL_SUMMONWB" }, + {WL_SUMMON_ATK_FIRE ,"WL_SUMMON_ATK_FIRE" }, + {WL_SUMMON_ATK_WIND ,"WL_SUMMON_ATK_WIND" }, + {WL_SUMMON_ATK_WATER ,"WL_SUMMON_ATK_WATER" }, + {WL_SUMMON_ATK_GROUND ,"WL_SUMMON_ATK_GROUND" }, + {WL_SUMMONSTONE ,"WL_SUMMONSTONE" }, + {WL_RELEASE ,"WL_RELEASE" }, + {WL_READING_SB ,"WL_READING_SB" }, + {WL_FREEZE_SP ,"WL_FREEZE_SP" }, + + //Ranger skills + {RA_ARROWSTORM ,"RA_ARROWSTORM" }, + {RA_FEARBREEZE ,"RA_FEARBREEZE" }, + {RA_RANGERMAIN ,"RA_RANGERMAIN" }, + {RA_AIMEDBOLT ,"RA_AIMEDBOLT" }, + {RA_DETONATOR ,"RA_DETONATOR" }, + {RA_ELECTRICSHOCKER ,"RA_ELECTRICSHOCKER" }, + {RA_CLUSTERBOMB ,"RA_CLUSTERBOMB" }, + {RA_WUGMASTERY ,"RA_WUGMASTERY" }, + {RA_WUGRIDER ,"RA_WUGRIDER" }, + {RA_WUGDASH ,"RA_WUGDASH" }, + {RA_WUGSTRIKE ,"RA_WUGSTRIKE" }, + {RA_WUGBITE ,"RA_WUGBITE" }, + {RA_TOOTHOFWUG ,"RA_TOOTHOFWUG" }, + {RA_SENSITIVEKEEN ,"RA_SENSITIVEKEEN" }, + {RA_CAMOUFLAGE ,"RA_CAMOUFLAGE" }, + {RA_RESEARCHTRAP ,"RA_RESEARCHTRAP" }, + {RA_MAGENTATRAP ,"RA_MAGENTATRAP" }, + {RA_COBALTTRAP ,"RA_COBALTTRAP" }, + {RA_MAIZETRAP ,"RA_MAIZETRAP" }, + {RA_VERDURETRAP ,"RA_VERDURETRAP" }, + {RA_FIRINGTRAP ,"RA_FIRINGTRAP" }, + {RA_ICEBOUNDTRAP ,"RA_ICEBOUNDTRAP" }, + + //Mechanic Skills + {NC_MADOLICENCE ,"NC_MADOLICENCE" }, + {NC_BOOSTKNUCKLE ,"NC_BOOSTKNUCKLE" }, + {NC_PILEBUNKER ,"NC_PILEBUNKER" }, + {NC_VULCANARM ,"NC_VULCANARM" }, + {NC_FLAMELAUNCHER ,"NC_FLAMELAUNCHER" }, + {NC_COLDSLOWER ,"NC_COLDSLOWER" }, + {NC_ARMSCANNON ,"NC_ARMSCANNON" }, + {NC_ACCELERATION ,"NC_ACCELERATION" }, + {NC_HOVERING ,"NC_HOVERING" }, + {NC_F_SIDESLIDE ,"NC_F_SIDESLIDE" }, + {NC_B_SIDESLIDE ,"NC_B_SIDESLIDE" }, + {NC_MAINFRAME ,"NC_MAINFRAME" }, + {NC_SELFDESTRUCTION ,"NC_SELFDESTRUCTION" }, + {NC_SHAPESHIFT ,"NC_SHAPESHIFT" }, + {NC_EMERGENCYCOOL ,"NC_EMERGENCYCOOL" }, + {NC_INFRAREDSCAN ,"NC_INFRAREDSCAN" }, + {NC_ANALYZE ,"NC_ANALYZE" }, + {NC_MAGNETICFIELD ,"NC_MAGNETICFIELD" }, + {NC_NEUTRALBARRIER ,"NC_NEUTRALBARRIER" }, + {NC_STEALTHFIELD ,"NC_STEALTHFIELD" }, + {NC_REPAIR ,"NC_REPAIR" }, + {NC_TRAININGAXE ,"NC_TRAININGAXE" }, + {NC_RESEARCHFE ,"NC_RESEARCHFE" }, + {NC_AXEBOOMERANG ,"NC_AXEBOOMERANG" }, + {NC_POWERSWING ,"NC_POWERSWING" }, + {NC_AXETORNADO ,"NC_AXETORNADO" }, + {NC_SILVERSNIPER ,"NC_SILVERSNIPER" }, + {NC_MAGICDECOY ,"NC_MAGICDECOY" }, + {NC_DISJOINT ,"NC_DISJOINT" }, + + //Shadow Chaser Skills + {SC_FATALMENACE ,"SC_FATALMENACE" }, + {SC_REPRODUCE ,"SC_REPRODUCE" }, + {SC_AUTOSHADOWSPELL ,"SC_AUTOSHADOWSPELL" }, + {SC_SHADOWFORM ,"SC_SHADOWFORM" }, + {SC_TRIANGLESHOT ,"SC_TRIANGLESHOT" }, + {SC_BODYPAINT ,"SC_BODYPAINT" }, + {SC_INVISIBILITY ,"SC_INVISIBILITY" }, + {SC_DEADLYINFECT ,"SC_DEADLYINFECT" }, + {SC_ENERVATION ,"SC_ENERVATION" }, + {SC_GROOMY ,"SC_GROOMY" }, + {SC_IGNORANCE ,"SC_IGNORANCE" }, + {SC_LAZINESS ,"SC_LAZINESS" }, + {SC_UNLUCKY ,"SC_UNLUCKY" }, + {SC_WEAKNESS ,"SC_WEAKNESS" }, + {SC_STRIPACCESSARY ,"SC_STRIPACCESSARY" }, + {SC_MANHOLE ,"SC_MANHOLE" }, + {SC_DIMENSIONDOOR ,"SC_DIMENSIONDOOR" }, + {SC_CHAOSPANIC ,"SC_CHAOSPANIC" }, + {SC_MAELSTROM ,"SC_MAELSTROM" }, + {SC_BLOODYLUST ,"SC_BLOODYLUST" }, + {SC_FEINTBOMB ,"SC_FEINTBOMB" }, + + //Royal Guard skills + {LG_CANNONSPEAR ,"LG_CANNONSPEAR" }, + {LG_BANISHINGPOINT ,"LG_BANISHINGPOINT" }, + {LG_TRAMPLE ,"LG_TRAMPLE" }, + {LG_SHIELDPRESS ,"LG_SHIELDPRESS" }, + {LG_REFLECTDAMAGE ,"LG_REFLECTDAMAGE" }, + {LG_PINPOINTATTACK ,"LG_PINPOINTATTACK" }, + {LG_FORCEOFVANGUARD ,"LG_FORCEOFVANGUARD" }, + {LG_RAGEBURST ,"LG_RAGEBURST" }, + {LG_SHIELDSPELL ,"LG_SHIELDSPELL" }, + {LG_EXEEDBREAK ,"LG_EXEEDBREAK" }, + {LG_OVERBRAND ,"LG_OVERBRAND" }, + {LG_PRESTIGE ,"LG_PRESTIGE" }, + {LG_BANDING ,"LG_BANDING" }, + {LG_MOONSLASHER ,"LG_MOONSLASHER" }, + {LG_RAYOFGENESIS ,"LG_RAYOFGENESIS" }, + {LG_PIETY ,"LG_PIETY" }, + {LG_EARTHDRIVE ,"LG_EARTHDRIVE" }, + {LG_HESPERUSLIT ,"LG_HESPERUSLIT" }, + {LG_INSPIRATION ,"LG_INSPIRATION" }, + + //Sura skills + {SR_DRAGONCOMBO ,"SR_DRAGONCOMBO" }, + {SR_SKYNETBLOW ,"SR_SKYNETBLOW" }, + {SR_EARTHSHAKER ,"SR_EARTHSHAKER" }, + {SR_FALLENEMPIRE ,"SR_FALLENEMPIRE" }, + {SR_TIGERCANNON ,"SR_TIGERCANNON" }, + {SR_HELLGATE ,"SR_HELLGATE" }, + {SR_RAMPAGEBLASTER ,"SR_RAMPAGEBLASTER" }, + {SR_CRESCENTELBOW ,"SR_CRESCENTELBOW" }, + {SR_CURSEDCIRCLE ,"SR_CURSEDCIRCLE" }, + {SR_LIGHTNINGWALK ,"SR_LIGHTNINGWALK" }, + {SR_KNUCKLEARROW ,"SR_KNUCKLEARROW" }, + {SR_WINDMILL ,"SR_WINDMILL" }, + {SR_RAISINGDRAGON ,"SR_RAISINGDRAGON" }, + {SR_GENTLETOUCH ,"SR_GENTLETOUCH" }, + {SR_ASSIMILATEPOWER ,"SR_ASSIMILATEPOWER" }, + {SR_POWERVELOCITY ,"SR_POWERVELOCITY" }, + {SR_CRESCENTELBOW_AUTOSPELL ,"SR_CRESCENTELBOW_AUTOSPELL" }, + {SR_GATEOFHELL ,"SR_GATEOFHELL" }, + {SR_GENTLETOUCH_QUIET ,"SR_GENTLETOUCH_QUIET" }, + {SR_GENTLETOUCH_CURE ,"SR_GENTLETOUCH_CURE" }, + {SR_GENTLETOUCH_ENERGYGAIN ,"SR_GENTLETOUCH_ENERGYGAIN" }, + {SR_GENTLETOUCH_CHANGE ,"SR_GENTLETOUCH_CHANGE" }, + {SR_GENTLETOUCH_REVITALIZE ,"SR_GENTLETOUCH_REVITALIZE" }, + + //Wanderer only skills + {WA_SWING_DANCE ,"WA_SWING_DANCE" }, + {WA_SYMPHONY_OF_LOVER ,"WA_SYMPHONY_OF_LOVER" }, + {WA_MOONLIT_SERENADE ,"WA_MOONLIT_SERENADE" }, + + //Ministrel only skills + {MI_RUSH_WINDMILL ,"MI_RUSH_WINDMILL" }, + {MI_ECHOSONG ,"MI_ECHOSONG" }, + {MI_HARMONIZE ,"MI_HARMONIZE" }, + + //Wanderer and Ministrel skills + {WM_LESSON ,"WM_LESSON" }, + {WM_METALICSOUND ,"WM_METALICSOUND" }, + {WM_REVERBERATION ,"WM_REVERBERATION" }, + {WM_REVERBERATION_MELEE ,"WM_REVERBERATION_MELEE" }, // Removed on kRO + {WM_REVERBERATION_MAGIC ,"WM_REVERBERATION_MAGIC" }, // Removed on kRO + {WM_DOMINION_IMPULSE ,"WM_DOMINION_IMPULSE" }, // Removed on kRO + {WM_SEVERE_RAINSTORM ,"WM_SEVERE_RAINSTORM" }, + {WM_POEMOFNETHERWORLD ,"WM_POEMOFNETHERWORLD" }, + {WM_VOICEOFSIREN ,"WM_VOICEOFSIREN" }, + {WM_DEADHILLHERE ,"WM_DEADHILLHERE" }, + {WM_LULLABY_DEEPSLEEP ,"WM_LULLABY_DEEPSLEEP" }, + {WM_SIRCLEOFNATURE ,"WM_SIRCLEOFNATURE" }, + {WM_RANDOMIZESPELL ,"WM_RANDOMIZESPELL" }, + {WM_GLOOMYDAY ,"WM_GLOOMYDAY" }, + {WM_GREAT_ECHO ,"WM_GREAT_ECHO" }, + {WM_SONG_OF_MANA ,"WM_SONG_OF_MANA" }, + {WM_DANCE_WITH_WUG ,"WM_DANCE_WITH_WUG" }, + {WM_SOUND_OF_DESTRUCTION ,"WM_SOUND_OF_DESTRUCTION" }, + {WM_SATURDAY_NIGHT_FEVER ,"WM_SATURDAY_NIGHT_FEVER" }, + {WM_LERADS_DEW ,"WM_LERADS_DEW" }, + {WM_MELODYOFSINK ,"WM_MELODYOFSINK" }, + {WM_BEYOND_OF_WARCRY ,"WM_BEYOND_OF_WARCRY" }, + {WM_UNLIMITED_HUMMING_VOICE ,"WM_UNLIMITED_HUMMING_VOICE" }, + + //Sorcerer skills + {SO_FIREWALK ,"SO_FIREWALK" }, + {SO_ELECTRICWALK ,"SO_ELECTRICWALK" }, + {SO_SPELLFIST ,"SO_SPELLFIST" }, + {SO_EARTHGRAVE ,"SO_EARTHGRAVE" }, + {SO_DIAMONDDUST ,"SO_DIAMONDDUST" }, + {SO_POISON_BUSTER ,"SO_POISON_BUSTER" }, + {SO_PSYCHIC_WAVE ,"SO_PSYCHIC_WAVE" }, + {SO_CLOUD_KILL ,"SO_CLOUD_KILL" }, + {SO_STRIKING ,"SO_STRIKING" }, + {SO_WARMER ,"SO_WARMER" }, + {SO_VACUUM_EXTREME ,"SO_VACUUM_EXTREME" }, + {SO_VARETYR_SPEAR ,"SO_VARETYR_SPEAR" }, + {SO_ARRULLO ,"SO_ARRULLO" }, + {SO_EL_CONTROL ,"SO_EL_CONTROL" }, + {SO_SUMMON_AGNI ,"SO_SUMMON_AGNI" }, + {SO_SUMMON_AQUA ,"SO_SUMMON_AQUA" }, + {SO_SUMMON_VENTUS ,"SO_SUMMON_VENTUS" }, + {SO_SUMMON_TERA ,"SO_SUMMON_TERA" }, + {SO_EL_ACTION ,"SO_EL_ACTION" }, + {SO_EL_ANALYSIS ,"SO_EL_ANALYSIS" }, + {SO_EL_SYMPATHY ,"SO_EL_SYMPATHY" }, + {SO_EL_CURE ,"SO_EL_CURE" }, + {SO_FIRE_INSIGNIA ,"SO_FIRE_INSIGNIA" }, + {SO_WATER_INSIGNIA ,"SO_WATER_INSIGNIA" }, + {SO_WIND_INSIGNIA ,"SO_WIND_INSIGNIA" }, + {SO_EARTH_INSIGNIA ,"SO_EARTH_INSIGNIA" }, + + //Genetic skills + {GN_TRAINING_SWORD ,"GN_TRAINING_SWORD" }, + {GN_REMODELING_CART ,"GN_REMODELING_CART" }, + {GN_CART_TORNADO ,"GN_CART_TORNADO" }, + {GN_CARTCANNON ,"GN_CARTCANNON" }, + {GN_CARTBOOST ,"GN_CARTBOOST" }, + {GN_THORNS_TRAP ,"GN_THORNS_TRAP" }, + {GN_BLOOD_SUCKER ,"GN_BLOOD_SUCKER" }, + {GN_SPORE_EXPLOSION ,"GN_SPORE_EXPLOSION" }, + {GN_WALLOFTHORN ,"GN_WALLOFTHORN" }, + {GN_CRAZYWEED ,"GN_CRAZYWEED" }, + {GN_CRAZYWEED_ATK ,"GN_CRAZYWEED_ATK" }, + {GN_DEMONIC_FIRE ,"GN_DEMONIC_FIRE" }, + {GN_FIRE_EXPANSION ,"GN_FIRE_EXPANSION" }, + {GN_FIRE_EXPANSION_SMOKE_POWDER ,"GN_FIRE_EXPANSION_SMOKE_POWDER" }, + {GN_FIRE_EXPANSION_TEAR_GAS ,"GN_FIRE_EXPANSION_TEAR_GAS" }, + {GN_FIRE_EXPANSION_ACID ,"GN_FIRE_EXPANSION_ACID" }, + {GN_HELLS_PLANT ,"GN_HELLS_PLANT" }, + {GN_HELLS_PLANT_ATK ,"GN_HELLS_PLANT_ATK" }, + {GN_MANDRAGORA ,"GN_MANDRAGORA" }, + {GN_SLINGITEM ,"GN_SLINGITEM" },// Removed on kRO + {GN_CHANGEMATERIAL ,"GN_CHANGEMATERIAL" }, + {GN_MIX_COOKING ,"GN_MIX_COOKING" }, + {GN_MAKEBOMB ,"GN_MAKEBOMB" },// Removed on kRO + {GN_S_PHARMACY ,"GN_S_PHARMACY" }, + {GN_SLINGITEM_RANGEMELEEATK ,"GN_SLINGITEM_RANGEMELEEATK" },// Removed on kRO + + //Additional quest skills + {AB_SECRAMENT ,"AB_SECRAMENT" }, + {WM_SEVERE_RAINSTORM_MELEE ,"WM_SEVERE_RAINSTORM_MELEE" }, + {SR_HOWLINGOFLION ,"SR_HOWLINGOFLION" }, + {SR_RIDEINLIGHTNING ,"SR_RIDEINLIGHTNING" }, + {LG_OVERBRAND_BRANDISH ,"LG_OVERBRAND_BRANDISH" }, + {LG_OVERBRAND_PLUSATK ,"LG_OVERBRAND_PLUSATK" }, + + //Additional Misc skills + {ALL_ODINS_RECALL ,"ALL_ODINS_RECALL" }, + {RETURN_TO_ELDICASTES ,"RETURN_TO_ELDICASTES" }, + {ALL_BUYING_STORE ,"ALL_BUYING_STORE" }, + {ALL_GUARDIAN_RECALL ,"ALL_GUARDIAN_RECALL" }, + {ALL_ODINS_POWER ,"ALL_ODINS_POWER" }, + {ALL_BEER_BOTTLE_CAP ,"ALL_BEER_BOTTLE_CAP" }, + {NPC_ASSASSINCROSS ,"NPC_ASSASSINCROSS" }, + {NPC_DISSONANCE ,"NPC_DISSONANCE" }, + {NPC_UGLYDANCE ,"NPC_UGLYDANCE" }, + {ALL_TETANY ,"ALL_TETANY" }, + {ALL_RAY_OF_PROTECTION ,"ALL_RAY_OF_PROTECTION" }, + {MC_CARTDECORATE ,"MC_CARTDECORATE" }, + {GM_ITEM_ATKMAX ,"GM_ITEM_ATKMAX" }, + {GM_ITEM_ATKMIN ,"GM_ITEM_ATKMIN" }, + {GM_ITEM_MATKMAX ,"GM_ITEM_MATKMAX" }, + {GM_ITEM_MATKMIN ,"GM_ITEM_MATKMIN" }, + + //Rebel skills + {RL_GLITTERING_GREED ,"RL_GLITTERING_GREED" }, + {RL_RICHS_COIN ,"RL_RICHS_COIN" }, + {RL_MASS_SPIRAL ,"RL_MASS_SPIRAL" }, + {RL_BANISHING_BUSTER ,"RL_BANISHING_BUSTER" }, + {RL_B_TRAP ,"RL_B_TRAP" }, + {RL_FLICKER ,"RL_FLICKER" }, + {RL_S_STORM ,"RL_S_STORM" }, + {RL_E_CHAIN ,"RL_E_CHAIN" }, + {RL_QD_SHOT ,"RL_QD_SHOT" }, + {RL_C_MARKER ,"RL_C_MARKER" }, + {RL_FIREDANCE ,"RL_FIREDANCE" }, + {RL_H_MINE ,"RL_H_MINE" }, + {RL_P_ALTER ,"RL_P_ALTER" }, + {RL_FALLEN_ANGEL ,"RL_FALLEN_ANGEL" }, + {RL_R_TRIP ,"RL_R_TRIP" }, + {RL_D_TAIL ,"RL_D_TAIL" }, + {RL_FIRE_RAIN ,"RL_FIRE_RAIN" }, + {RL_HEAT_BARREL ,"RL_HEAT_BARREL" }, + {RL_AM_BLAST ,"RL_AM_BLAST" }, + {RL_SLUGSHOT ,"RL_SLUGSHOT" }, + {RL_HAMMER_OF_GOD ,"RL_HAMMER_OF_GOD" }, + {RL_R_TRIP_PLUSATK ,"RL_R_TRIP_PLUSATK" }, + {RL_B_FLICKER_ATK ,"RL_B_FLICKER_ATK" }, + // RL_GLITTERING_GREED_ATK, + + // Star Emperor skills + {SJ_LIGHTOFMOON ,"SJ_LIGHTOFMOON" }, + {SJ_LUNARSTANCE ,"SJ_LUNARSTANCE" }, + {SJ_FULLMOONKICK ,"SJ_FULLMOONKICK" }, + {SJ_LIGHTOFSTAR ,"SJ_LIGHTOFSTAR" }, + {SJ_STARSTANCE ,"SJ_STARSTANCE" }, + {SJ_NEWMOONKICK ,"SJ_NEWMOONKICK" }, + {SJ_FLASHKICK ,"SJ_FLASHKICK" }, + {SJ_STAREMPEROR ,"SJ_STAREMPEROR" }, + {SJ_NOVAEXPLOSING ,"SJ_NOVAEXPLOSING" }, + {SJ_UNIVERSESTANCE ,"SJ_UNIVERSESTANCE" }, + {SJ_FALLINGSTAR ,"SJ_FALLINGSTAR" }, + {SJ_GRAVITYCONTROL ,"SJ_GRAVITYCONTROL" }, + {SJ_BOOKOFDIMENSION ,"SJ_BOOKOFDIMENSION" }, + {SJ_BOOKOFCREATINGSTAR ,"SJ_BOOKOFCREATINGSTAR" }, + {SJ_DOCUMENT ,"SJ_DOCUMENT" }, + {SJ_PURIFY ,"SJ_PURIFY" }, + {SJ_LIGHTOFSUN ,"SJ_LIGHTOFSUN" }, + {SJ_SUNSTANCE ,"SJ_SUNSTANCE" }, + {SJ_SOLARBURST ,"SJ_SOLARBURST" }, + {SJ_PROMINENCEKICK ,"SJ_PROMINENCEKICK" }, + {SJ_FALLINGSTAR_ATK ,"SJ_FALLINGSTAR_ATK" }, + {SJ_FALLINGSTAR_ATK2 ,"SJ_FALLINGSTAR_ATK2" }, + + //Soul Reaper Skills + {SP_SOULGOLEM ,"SP_SOULGOLEM" }, + {SP_SOULSHADOW ,"SP_SOULSHADOW" }, + {SP_SOULFALCON ,"SP_SOULFALCON" }, + {SP_SOULFAIRY ,"SP_SOULFAIRY" }, + {SP_CURSEEXPLOSION ,"SP_CURSEEXPLOSION" }, + {SP_SOULCURSE ,"SP_SOULCURSE" }, + {SP_SPA ,"SP_SPA" }, + {SP_SHA ,"SP_SHA" }, + {SP_SWHOO ,"SP_SWHOO" }, + {SP_SOULUNITY ,"SP_SOULUNITY" }, + {SP_SOULDIVISION ,"SP_SOULDIVISION" }, + {SP_SOULREAPER ,"SP_SOULREAPER" }, + {SP_SOULREVOLVE ,"SP_SOULREVOLVE" }, + {SP_SOULCOLLECT ,"SP_SOULCOLLECT" }, + {SP_SOULEXPLOSION ,"SP_SOULEXPLOSION" }, + {SP_SOULENERGY ,"SP_SOULENERGY" }, + {SP_KAUTE ,"SP_KAUTE" }, + + //Kagero and Oboro skills + {KO_YAMIKUMO ,"KO_YAMIKUMO" }, + {KO_RIGHT ,"KO_RIGHT" }, + {KO_LEFT ,"KO_LEFT" }, + {KO_JYUMONJIKIRI ,"KO_JYUMONJIKIRI" }, + {KO_SETSUDAN ,"KO_SETSUDAN" }, + {KO_BAKURETSU ,"KO_BAKURETSU" }, + {KO_HAPPOKUNAI ,"KO_HAPPOKUNAI" }, + {KO_MUCHANAGE ,"KO_MUCHANAGE" }, + {KO_HUUMARANKA ,"KO_HUUMARANKA" }, + {KO_MAKIBISHI ,"KO_MAKIBISHI" }, + {KO_MEIKYOUSISUI ,"KO_MEIKYOUSISUI" }, + {KO_ZANZOU ,"KO_ZANZOU" }, + {KO_KYOUGAKU ,"KO_KYOUGAKU" }, + {KO_JYUSATSU ,"KO_JYUSATSU" }, + {KO_KAHU_ENTEN ,"KO_KAHU_ENTEN" }, + {KO_HYOUHU_HUBUKI ,"KO_HYOUHU_HUBUKI" }, + {KO_KAZEHU_SEIRAN ,"KO_KAZEHU_SEIRAN" }, + {KO_DOHU_KOUKAI ,"KO_DOHU_KOUKAI" }, + {KO_KAIHOU ,"KO_KAIHOU" }, + {KO_ZENKAI ,"KO_ZENKAI" }, + {KO_GENWAKU ,"KO_GENWAKU" }, + {KO_IZAYOI ,"KO_IZAYOI" }, + //Kagero only skills + {KG_KAGEHUMI ,"KG_KAGEHUMI" }, + {KG_KYOMU ,"KG_KYOMU" }, + {KG_KAGEMUSYA ,"KG_KAGEMUSYA" }, + //Oboro only skills + {OB_ZANGETSU ,"OB_ZANGETSU" }, + {OB_OBOROGENSOU ,"OB_OBOROGENSOU" }, + {OB_OBOROGENSOU_TRANSITION_ATK ,"OB_OBOROGENSOU_TRANSITION_ATK" }, + {OB_AKAITSUKI ,"OB_AKAITSUKI" }, + + {ECL_SNOWFLIP ,"ECL_SNOWFLIP" }, + {ECL_PEONYMAMY ,"ECL_PEONYMAMY" }, + {ECL_SADAGUI ,"ECL_SADAGUI" }, + {ECL_SEQUOIADUST ,"ECL_SEQUOIADUST" }, + {ECLAGE_RECALL ,"ECLAGE_RECALL" }, + + {ALL_PRONTERA_RECALL ,"ALL_PRONTERA_RECALL" }, + {ALL_GLASTHEIM_RECALL ,"ALL_GLASTHEIM_RECALL" }, + {ALL_THANATOS_RECALL ,"ALL_THANATOS_RECALL" }, + {ALL_LIGHTHALZEN_RECALL ,"ALL_LIGHTHALZEN_RECALL" }, + + //Additional quest skills + {GC_DARKCROW ,"GC_DARKCROW" }, + {RA_UNLIMIT ,"RA_UNLIMIT" }, + {GN_ILLUSIONDOPING ,"GN_ILLUSIONDOPING" }, + {RK_DRAGONBREATH_WATER ,"RK_DRAGONBREATH_WATER" }, + {RK_LUXANIMA ,"RK_LUXANIMA" }, + {NC_MAGMA_ERUPTION ,"NC_MAGMA_ERUPTION" }, + {WM_FRIGG_SONG ,"WM_FRIGG_SONG" }, + {SO_ELEMENTAL_SHIELD ,"SO_ELEMENTAL_SHIELD" }, + {SR_FLASHCOMBO ,"SR_FLASHCOMBO" }, + {SC_ESCAPE ,"SC_ESCAPE" }, + {AB_OFFERTORIUM ,"AB_OFFERTORIUM" }, + {WL_TELEKINESIS_INTENSE ,"WL_TELEKINESIS_INTENSE" }, + {LG_KINGS_GRACE ,"LG_KINGS_GRACE" }, + {ALL_FULL_THROTTLE ,"ALL_FULL_THROTTLE" }, + {NC_MAGMA_ERUPTION_DOTDAMAGE ,"NC_MAGMA_ERUPTION_DOTDAMAGE" }, + + //Summoner skills + {SU_BASIC_SKILL ,"SU_BASIC_SKILL" }, + {SU_BITE ,"SU_BITE" }, + {SU_HIDE ,"SU_HIDE" }, + {SU_SCRATCH ,"SU_SCRATCH" }, + {SU_STOOP ,"SU_STOOP" }, + {SU_LOPE ,"SU_LOPE" }, + {SU_SPRITEMABLE ,"SU_SPRITEMABLE" }, + {SU_POWEROFLAND ,"SU_POWEROFLAND" }, + {SU_SV_STEMSPEAR ,"SU_SV_STEMSPEAR" }, + {SU_CN_POWDERING ,"SU_CN_POWDERING" }, + {SU_CN_METEOR ,"SU_CN_METEOR" }, + {SU_SV_ROOTTWIST ,"SU_SV_ROOTTWIST" }, + {SU_SV_ROOTTWIST_ATK ,"SU_SV_ROOTTWIST_ATK" }, + {SU_POWEROFLIFE ,"SU_POWEROFLIFE" }, + {SU_SCAROFTAROU ,"SU_SCAROFTAROU" }, + {SU_PICKYPECK ,"SU_PICKYPECK" }, + {SU_PICKYPECK_DOUBLE_ATK ,"SU_PICKYPECK_DOUBLE_ATK" }, + {SU_ARCLOUSEDASH ,"SU_ARCLOUSEDASH" }, + {SU_LUNATICCARROTBEAT ,"SU_LUNATICCARROTBEAT" }, + {SU_POWEROFSEA ,"SU_POWEROFSEA" }, + {SU_TUNABELLY ,"SU_TUNABELLY" }, + {SU_TUNAPARTY ,"SU_TUNAPARTY" }, + {SU_BUNCHOFSHRIMP ,"SU_BUNCHOFSHRIMP" }, + {SU_FRESHSHRIMP ,"SU_FRESHSHRIMP" }, + {SU_CN_METEOR2 ,"SU_CN_METEOR2" }, + {SU_LUNATICCARROTBEAT2 ,"SU_LUNATICCARROTBEAT2" }, + {SU_SOULATTACK ,"SU_SOULATTACK" }, + {SU_POWEROFFLOCK ,"SU_POWEROFFLOCK" }, + {SU_SVG_SPIRIT ,"SU_SVG_SPIRIT" }, + {SU_HISS ,"SU_HISS" }, + {SU_NYANGGRASS ,"SU_NYANGGRASS" }, + {SU_GROOMING ,"SU_GROOMING" }, + {SU_PURRING ,"SU_PURRING" }, + {SU_SHRIMPARTY ,"SU_SHRIMPARTY" }, + {SU_SPIRITOFLIFE ,"SU_SPIRITOFLIFE" }, + {SU_MEOWMEOW ,"SU_MEOWMEOW" }, + {SU_SPIRITOFLAND ,"SU_SPIRITOFLAND" }, + {SU_CHATTERING ,"SU_CHATTERING" }, + {SU_SPIRITOFSEA ,"SU_SPIRITOFSEA" }, + + //Wedding skills, + {WE_CALLALLFAMILY ,"WE_CALLALLFAMILY" }, + {WE_ONEFOREVER ,"WE_ONEFOREVER" }, + {WE_CHEERUP ,"WE_CHEERUP" }, + + {ALL_EQSWITCH ,"ALL_EQSWITCH" }, + + {CG_SPECIALSINGER ,"CG_SPECIALSINGER" }, + + {AB_VITUPERATUM ,"AB_VITUPERATUM" }, + {AB_CONVENIO ,"AB_CONVENIO" }, + {ALL_LIGHTNING_STORM ,"ALL_LIGHTNING_STORM" }, + {NV_BREAKTHROUGH ,"NV_BREAKTHROUGH" }, + {NV_HELPANGEL ,"NV_HELPANGEL" }, + {NV_TRANSCENDENCE ,"NV_TRANSCENDENCE" }, + {WL_READING_SB_READING ,"WL_READING_SB_READING" }, + + //Dragon Knight skills + {DK_SERVANTWEAPON ,"DK_SERVANTWEAPON" }, + {DK_SERVANTWEAPON_ATK ,"DK_SERVANTWEAPON_ATK" }, + {DK_SERVANT_W_SIGN ,"DK_SERVANT_W_SIGN" }, + {DK_SERVANT_W_PHANTOM ,"DK_SERVANT_W_PHANTOM" }, + {DK_SERVANT_W_DEMOL ,"DK_SERVANT_W_DEMOL" }, + {DK_CHARGINGPIERCE ,"DK_CHARGINGPIERCE" }, + {DK_TWOHANDDEF ,"DK_TWOHANDDEF" }, + {DK_HACKANDSLASHER ,"DK_HACKANDSLASHER" }, + {DK_HACKANDSLASHER_ATK ,"DK_HACKANDSLASHER_ATK" }, + {DK_DRAGONIC_AURA ,"DK_DRAGONIC_AURA" }, + {DK_MADNESS_CRUSHER ,"DK_MADNESS_CRUSHER" }, + {DK_VIGOR ,"DK_VIGOR" }, + {DK_STORMSLASH ,"DK_STORMSLASH" }, + + //Arch Mage skills + {AG_DEADLY_PROJECTION ,"AG_DEADLY_PROJECTION" }, + {AG_DESTRUCTIVE_HURRICANE ,"AG_DESTRUCTIVE_HURRICANE" }, + {AG_RAIN_OF_CRYSTAL ,"AG_RAIN_OF_CRYSTAL" }, + {AG_MYSTERY_ILLUSION ,"AG_MYSTERY_ILLUSION" }, + {AG_VIOLENT_QUAKE ,"AG_VIOLENT_QUAKE" }, + {AG_VIOLENT_QUAKE_ATK ,"AG_VIOLENT_QUAKE_ATK" }, + {AG_SOUL_VC_STRIKE ,"AG_SOUL_VC_STRIKE" }, + {AG_STRANTUM_TREMOR ,"AG_STRANTUM_TREMOR" }, + {AG_ALL_BLOOM ,"AG_ALL_BLOOM" }, + {AG_ALL_BLOOM_ATK ,"AG_ALL_BLOOM_ATK" }, + {AG_ALL_BLOOM_ATK2 ,"AG_ALL_BLOOM_ATK2" }, + {AG_CRYSTAL_IMPACT ,"AG_CRYSTAL_IMPACT" }, + {AG_CRYSTAL_IMPACT_ATK ,"AG_CRYSTAL_IMPACT_ATK" }, + {AG_TORNADO_STORM ,"AG_TORNADO_STORM" }, + {AG_TWOHANDSTAFF ,"AG_TWOHANDSTAFF" }, + {AG_FLORAL_FLARE_ROAD ,"AG_FLORAL_FLARE_ROAD" }, + {AG_ASTRAL_STRIKE ,"AG_ASTRAL_STRIKE" }, + {AG_ASTRAL_STRIKE_ATK ,"AG_ASTRAL_STRIKE_ATK" }, + {AG_CLIMAX ,"AG_CLIMAX" }, + {AG_ROCK_DOWN ,"AG_ROCK_DOWN" }, + {AG_STORM_CANNON ,"AG_STORM_CANNON" }, + {AG_CRIMSON_ARROW ,"AG_CRIMSON_ARROW" }, + {AG_CRIMSON_ARROW_ATK ,"AG_CRIMSON_ARROW_ATK" }, + {AG_FROZEN_SLASH ,"AG_FROZEN_SLASH" }, + + //Inquisitor skills + {IQ_POWERFUL_FAITH ,"IQ_POWERFUL_FAITH" }, + {IQ_FIRM_FAITH ,"IQ_FIRM_FAITH" }, + {IQ_WILL_OF_FAITH ,"IQ_WILL_OF_FAITH" }, + {IQ_OLEUM_SANCTUM ,"IQ_OLEUM_SANCTUM" }, + {IQ_SINCERE_FAITH ,"IQ_SINCERE_FAITH" }, + {IQ_MASSIVE_F_BLASTER ,"IQ_MASSIVE_F_BLASTER" }, + {IQ_EXPOSION_BLASTER ,"IQ_EXPOSION_BLASTER" }, + {IQ_FIRST_BRAND ,"IQ_FIRST_BRAND" }, + {IQ_FIRST_FAITH_POWER ,"IQ_FIRST_FAITH_POWER" }, + {IQ_JUDGE ,"IQ_JUDGE" }, + {IQ_SECOND_FLAME ,"IQ_SECOND_FLAME" }, + {IQ_SECOND_FAITH ,"IQ_SECOND_FAITH" }, + {IQ_SECOND_JUDGEMENT ,"IQ_SECOND_JUDGEMENT" }, + {IQ_THIRD_PUNISH ,"IQ_THIRD_PUNISH" }, + {IQ_THIRD_FLAME_BOMB ,"IQ_THIRD_FLAME_BOMB" }, + {IQ_THIRD_CONSECRATION ,"IQ_THIRD_CONSECRATION" }, + {IQ_THIRD_EXOR_FLAME ,"IQ_THIRD_EXOR_FLAME" }, + + //Imperial Guard skills + {IG_GUARD_STANCE ,"IG_GUARD_STANCE" }, + {IG_GUARDIAN_SHIELD ,"IG_GUARDIAN_SHIELD" }, + {IG_REBOUND_SHIELD ,"IG_REBOUND_SHIELD" }, + {IG_SHIELD_MASTERY ,"IG_SHIELD_MASTERY" }, + {IG_SPEAR_SWORD_M ,"IG_SPEAR_SWORD_M" }, + {IG_ATTACK_STANCE ,"IG_ATTACK_STANCE" }, + {IG_ULTIMATE_SACRIFICE ,"IG_ULTIMATE_SACRIFICE" }, + {IG_HOLY_SHIELD ,"IG_HOLY_SHIELD" }, + {IG_GRAND_JUDGEMENT ,"IG_GRAND_JUDGEMENT" }, + {IG_JUDGEMENT_CROSS ,"IG_JUDGEMENT_CROSS" }, + {IG_SHIELD_SHOOTING ,"IG_SHIELD_SHOOTING" }, + {IG_OVERSLASH ,"IG_OVERSLASH" }, + {IG_CROSS_RAIN ,"IG_CROSS_RAIN" }, + + //Cardinal skills + {CD_REPARATIO ,"CD_REPARATIO" }, + {CD_MEDIALE_VOTUM ,"CD_MEDIALE_VOTUM" }, + {CD_MACE_BOOK_M ,"CD_MACE_BOOK_M" }, + {CD_ARGUTUS_VITA ,"CD_ARGUTUS_VITA" }, + {CD_ARGUTUS_TELUM ,"CD_ARGUTUS_TELUM" }, + {CD_ARBITRIUM ,"CD_ARBITRIUM" }, + {CD_ARBITRIUM_ATK ,"CD_ARBITRIUM_ATK" }, + {CD_PRESENS_ACIES ,"CD_PRESENS_ACIES" }, + {CD_FIDUS_ANIMUS ,"CD_FIDUS_ANIMUS" }, + {CD_EFFLIGO ,"CD_EFFLIGO" }, + {CD_COMPETENTIA ,"CD_COMPETENTIA" }, + {CD_PNEUMATICUS_PROCELLA ,"CD_PNEUMATICUS_PROCELLA" }, + {CD_DILECTIO_HEAL ,"CD_DILECTIO_HEAL" }, + {CD_RELIGIO ,"CD_RELIGIO" }, + {CD_BENEDICTUM ,"CD_BENEDICTUM" }, + {CD_PETITIO ,"CD_PETITIO" }, + {CD_FRAMEN ,"CD_FRAMEN" }, + + //Shadow Cross skills + {SHC_SHADOW_EXCEED ,"SHC_SHADOW_EXCEED" }, + {SHC_DANCING_KNIFE ,"SHC_DANCING_KNIFE" }, + {SHC_SAVAGE_IMPACT ,"SHC_SAVAGE_IMPACT" }, + {SHC_SHADOW_SENSE ,"SHC_SHADOW_SENSE" }, + {SHC_ETERNAL_SLASH ,"SHC_ETERNAL_SLASH" }, + {SHC_POTENT_VENOM ,"SHC_POTENT_VENOM" }, + {SHC_SHADOW_STAB ,"SHC_SHADOW_STAB" }, + {SHC_IMPACT_CRATER ,"SHC_IMPACT_CRATER" }, + {SHC_ENCHANTING_SHADOW ,"SHC_ENCHANTING_SHADOW" }, + {SHC_FATAL_SHADOW_CROW ,"SHC_FATAL_SHADOW_CROW" }, + + //Meister skills + {MT_AXE_STOMP ,"MT_AXE_STOMP" }, + {MT_RUSH_QUAKE ,"MT_RUSH_QUAKE" }, + {MT_M_MACHINE ,"MT_M_MACHINE" }, + {MT_A_MACHINE ,"MT_A_MACHINE" }, + {MT_D_MACHINE ,"MT_D_MACHINE" }, + {MT_TWOAXEDEF ,"MT_TWOAXEDEF" }, + {MT_ABR_M ,"MT_ABR_M" }, + {MT_SUMMON_ABR_BATTLE_WARIOR ,"MT_SUMMON_ABR_BATTLE_WARIOR" }, + {MT_SUMMON_ABR_DUAL_CANNON ,"MT_SUMMON_ABR_DUAL_CANNON" }, + {MT_SUMMON_ABR_MOTHER_NET ,"MT_SUMMON_ABR_MOTHER_NET" }, + {MT_SUMMON_ABR_INFINITY ,"MT_SUMMON_ABR_INFINITY" }, + + //Additional 4th skills + {AG_DESTRUCTIVE_HURRICANE_CLIMAX ,"AG_DESTRUCTIVE_HURRICANE_CLIMAX" }, + {BO_ACIDIFIED_ZONE_WATER_ATK ,"BO_ACIDIFIED_ZONE_WATER_ATK" }, + {BO_ACIDIFIED_ZONE_GROUND_ATK ,"BO_ACIDIFIED_ZONE_GROUND_ATK" }, + {BO_ACIDIFIED_ZONE_WIND_ATK ,"BO_ACIDIFIED_ZONE_WIND_ATK" }, + {BO_ACIDIFIED_ZONE_FIRE_ATK ,"BO_ACIDIFIED_ZONE_FIRE_ATK" }, + + //Abyss Chaser skills + {ABC_DAGGER_AND_BOW_M ,"ABC_DAGGER_AND_BOW_M" }, + {ABC_MAGIC_SWORD_M ,"ABC_MAGIC_SWORD_M" }, + {ABC_STRIP_SHADOW ,"ABC_STRIP_SHADOW" }, + {ABC_ABYSS_DAGGER ,"ABC_ABYSS_DAGGER" }, + {ABC_UNLUCKY_RUSH ,"ABC_UNLUCKY_RUSH" }, + {ABC_CHAIN_REACTION_SHOT ,"ABC_CHAIN_REACTION_SHOT" }, + {ABC_FROM_THE_ABYSS ,"ABC_FROM_THE_ABYSS" }, + {ABC_ABYSS_SLAYER ,"ABC_ABYSS_SLAYER" }, + {ABC_ABYSS_STRIKE ,"ABC_ABYSS_STRIKE" }, + {ABC_DEFT_STAB ,"ABC_DEFT_STAB" }, + {ABC_ABYSS_SQUARE ,"ABC_ABYSS_SQUARE" }, + {ABC_FRENZY_SHOT ,"ABC_FRENZY_SHOT" }, + + //Windhawk skills + {WH_ADVANCED_TRAP ,"WH_ADVANCED_TRAP" }, + {WH_WIND_SIGN ,"WH_WIND_SIGN" }, + {WH_NATUREFRIENDLY ,"WH_NATUREFRIENDLY" }, + {WH_HAWKRUSH ,"WH_HAWKRUSH" }, + {WH_HAWK_M ,"WH_HAWK_M" }, + {WH_CALAMITYGALE ,"WH_CALAMITYGALE" }, + {WH_HAWKBOOMERANG ,"WH_HAWKBOOMERANG" }, + {WH_GALESTORM ,"WH_GALESTORM" }, + {WH_DEEPBLINDTRAP ,"WH_DEEPBLINDTRAP" }, + {WH_SOLIDTRAP ,"WH_SOLIDTRAP" }, + {WH_SWIFTTRAP ,"WH_SWIFTTRAP" }, + {WH_CRESCIVE_BOLT ,"WH_CRESCIVE_BOLT" }, + {WH_FLAMETRAP ,"WH_FLAMETRAP" }, + + //Biolo skills + {BO_BIONIC_PHARMACY ,"BO_BIONIC_PHARMACY" }, + {BO_BIONICS_M ,"BO_BIONICS_M" }, + {BO_THE_WHOLE_PROTECTION ,"BO_THE_WHOLE_PROTECTION" }, + {BO_ADVANCE_PROTECTION ,"BO_ADVANCE_PROTECTION" }, + {BO_ACIDIFIED_ZONE_WATER ,"BO_ACIDIFIED_ZONE_WATER" }, + {BO_ACIDIFIED_ZONE_GROUND ,"BO_ACIDIFIED_ZONE_GROUND" }, + {BO_ACIDIFIED_ZONE_WIND ,"BO_ACIDIFIED_ZONE_WIND" }, + {BO_ACIDIFIED_ZONE_FIRE ,"BO_ACIDIFIED_ZONE_FIRE" }, + {BO_WOODENWARRIOR ,"BO_WOODENWARRIOR" }, + {BO_WOODEN_FAIRY ,"BO_WOODEN_FAIRY" }, + {BO_CREEPER ,"BO_CREEPER" }, + {BO_RESEARCHREPORT ,"BO_RESEARCHREPORT" }, + {BO_HELLTREE ,"BO_HELLTREE" }, + + //Troubadour/Trouvere skills + {TR_STAGE_MANNER ,"TR_STAGE_MANNER" }, + {TR_RETROSPECTION ,"TR_RETROSPECTION" }, + {TR_MYSTIC_SYMPHONY ,"TR_MYSTIC_SYMPHONY" }, + {TR_KVASIR_SONATA ,"TR_KVASIR_SONATA" }, + {TR_ROSEBLOSSOM ,"TR_ROSEBLOSSOM" }, + {TR_ROSEBLOSSOM_ATK ,"TR_ROSEBLOSSOM_ATK" }, + {TR_RHYTHMSHOOTING ,"TR_RHYTHMSHOOTING" }, + {TR_METALIC_FURY ,"TR_METALIC_FURY" }, + {TR_SOUNDBLEND ,"TR_SOUNDBLEND" }, + {TR_GEF_NOCTURN ,"TR_GEF_NOCTURN" }, + {TR_ROKI_CAPRICCIO ,"TR_ROKI_CAPRICCIO" }, + {TR_AIN_RHAPSODY ,"TR_AIN_RHAPSODY" }, + {TR_MUSICAL_INTERLUDE ,"TR_MUSICAL_INTERLUDE" }, + {TR_JAWAII_SERENADE ,"TR_JAWAII_SERENADE" }, + {TR_NIPELHEIM_REQUIEM ,"TR_NIPELHEIM_REQUIEM" }, + {TR_PRON_MARCH ,"TR_PRON_MARCH" }, + + //Elemental Master skills + {EM_MAGIC_BOOK_M ,"EM_MAGIC_BOOK_M" }, + {EM_SPELL_ENCHANTING ,"EM_SPELL_ENCHANTING" }, + {EM_ACTIVITY_BURN ,"EM_ACTIVITY_BURN" }, + {EM_INCREASING_ACTIVITY ,"EM_INCREASING_ACTIVITY" }, + {EM_DIAMOND_STORM ,"EM_DIAMOND_STORM" }, + {EM_LIGHTNING_LAND ,"EM_LIGHTNING_LAND" }, + {EM_VENOM_SWAMP ,"EM_VENOM_SWAMP" }, + {EM_CONFLAGRATION ,"EM_CONFLAGRATION" }, + {EM_TERRA_DRIVE ,"EM_TERRA_DRIVE" }, + {EM_ELEMENTAL_SPIRIT_M ,"EM_ELEMENTAL_SPIRIT_M" }, + {EM_SUMMON_ELEMENTAL_ARDOR ,"EM_SUMMON_ELEMENTAL_ARDOR" }, + {EM_SUMMON_ELEMENTAL_DILUVIO ,"EM_SUMMON_ELEMENTAL_DILUVIO" }, + {EM_SUMMON_ELEMENTAL_PROCELLA ,"EM_SUMMON_ELEMENTAL_PROCELLA" }, + {EM_SUMMON_ELEMENTAL_TERREMOTUS ,"EM_SUMMON_ELEMENTAL_TERREMOTUS" }, + {EM_SUMMON_ELEMENTAL_SERPENS ,"EM_SUMMON_ELEMENTAL_SERPENS" }, + {EM_ELEMENTAL_BUSTER ,"EM_ELEMENTAL_BUSTER" }, + {EM_ELEMENTAL_VEIL ,"EM_ELEMENTAL_VEIL" }, + + //Additional quest skills + {ABC_CHAIN_REACTION_SHOT_ATK ,"ABC_CHAIN_REACTION_SHOT_ATK" }, + {ABC_FROM_THE_ABYSS_ATK ,"ABC_FROM_THE_ABYSS_ATK" }, + {BO_WOODEN_THROWROCK ,"BO_WOODEN_THROWROCK" }, + {BO_WOODEN_ATTACK ,"BO_WOODEN_ATTACK" }, + {BO_HELL_HOWLING ,"BO_HELL_HOWLING" }, + {BO_HELL_DUSTY ,"BO_HELL_DUSTY" }, + {BO_FAIRY_DUSTY ,"BO_FAIRY_DUSTY" }, + {EM_ELEMENTAL_BUSTER_FIRE ,"EM_ELEMENTAL_BUSTER_FIRE" }, + {EM_ELEMENTAL_BUSTER_WATER ,"EM_ELEMENTAL_BUSTER_WATER" }, + {EM_ELEMENTAL_BUSTER_WIND ,"EM_ELEMENTAL_BUSTER_WIND" }, + {EM_ELEMENTAL_BUSTER_GROUND ,"EM_ELEMENTAL_BUSTER_GROUND" }, + {EM_ELEMENTAL_BUSTER_POISON ,"EM_ELEMENTAL_BUSTER_POISON" }, + + //Nightwatch skills + {NW_P_F_I ,"NW_P_F_I" }, + {NW_GRENADE_MASTERY ,"NW_GRENADE_MASTERY" }, + {NW_INTENSIVE_AIM ,"NW_INTENSIVE_AIM" }, + {NW_GRENADE_FRAGMENT ,"NW_GRENADE_FRAGMENT" }, + {NW_THE_VIGILANTE_AT_NIGHT ,"NW_THE_VIGILANTE_AT_NIGHT" }, + {NW_ONLY_ONE_BULLET ,"NW_ONLY_ONE_BULLET" }, + {NW_SPIRAL_SHOOTING ,"NW_SPIRAL_SHOOTING" }, + {NW_MAGAZINE_FOR_ONE ,"NW_MAGAZINE_FOR_ONE" }, + {NW_WILD_FIRE ,"NW_WILD_FIRE" }, + {NW_BASIC_GRENADE ,"NW_BASIC_GRENADE" }, + {NW_HASTY_FIRE_IN_THE_HOLE ,"NW_HASTY_FIRE_IN_THE_HOLE" }, + {NW_GRENADES_DROPPING ,"NW_GRENADES_DROPPING" }, + {NW_AUTO_FIRING_LAUNCHER ,"NW_AUTO_FIRING_LAUNCHER" }, + {NW_HIDDEN_CARD ,"NW_HIDDEN_CARD" }, + {NW_MISSION_BOMBARD ,"NW_MISSION_BOMBARD" }, + + //Soul Ascedic skills + {SOA_TALISMAN_MASTERY ,"SOA_TALISMAN_MASTERY" }, + {SOA_SOUL_MASTERY ,"SOA_SOUL_MASTERY" }, + {SOA_TALISMAN_OF_PROTECTION ,"SOA_TALISMAN_OF_PROTECTION" }, + {SOA_TALISMAN_OF_WARRIOR ,"SOA_TALISMAN_OF_WARRIOR" }, + {SOA_TALISMAN_OF_MAGICIAN ,"SOA_TALISMAN_OF_MAGICIAN" }, + {SOA_SOUL_GATHERING ,"SOA_SOUL_GATHERING" }, + {SOA_TOTEM_OF_TUTELARY ,"SOA_TOTEM_OF_TUTELARY" }, + {SOA_TALISMAN_OF_FIVE_ELEMENTS ,"SOA_TALISMAN_OF_FIVE_ELEMENTS" }, + {SOA_TALISMAN_OF_SOUL_STEALING ,"SOA_TALISMAN_OF_SOUL_STEALING" }, + {SOA_EXORCISM_OF_MALICIOUS_SOUL ,"SOA_EXORCISM_OF_MALICIOUS_SOUL" }, + {SOA_TALISMAN_OF_BLUE_DRAGON ,"SOA_TALISMAN_OF_BLUE_DRAGON" }, + {SOA_TALISMAN_OF_WHITE_TIGER ,"SOA_TALISMAN_OF_WHITE_TIGER" }, + {SOA_TALISMAN_OF_RED_PHOENIX ,"SOA_TALISMAN_OF_RED_PHOENIX" }, + {SOA_TALISMAN_OF_BLACK_TORTOISE ,"SOA_TALISMAN_OF_BLACK_TORTOISE" }, + {SOA_TALISMAN_OF_FOUR_BEARING_GOD ,"SOA_TALISMAN_OF_FOUR_BEARING_GOD" }, + {SOA_CIRCLE_OF_DIRECTIONS_AND_ELEMENTALS ,"SOA_CIRCLE_OF_DIRECTIONS_AND_ELEMENTALS" }, + {SOA_SOUL_OF_HEAVEN_AND_EARTH ,"SOA_SOUL_OF_HEAVEN_AND_EARTH" }, + + //Spirit Handler skills + {SH_MYSTICAL_CREATURE_MASTERY ,"SH_MYSTICAL_CREATURE_MASTERY" }, + {SH_COMMUNE_WITH_CHUL_HO ,"SH_COMMUNE_WITH_CHUL_HO" }, + {SH_CHUL_HO_SONIC_CLAW ,"SH_CHUL_HO_SONIC_CLAW" }, + {SH_HOWLING_OF_CHUL_HO ,"SH_HOWLING_OF_CHUL_HO" }, + {SH_HOGOGONG_STRIKE ,"SH_HOGOGONG_STRIKE" }, + {SH_COMMUNE_WITH_KI_SUL ,"SH_COMMUNE_WITH_KI_SUL" }, + {SH_KI_SUL_WATER_SPRAYING ,"SH_KI_SUL_WATER_SPRAYING" }, + {SH_MARINE_FESTIVAL_OF_KI_SUL ,"SH_MARINE_FESTIVAL_OF_KI_SUL" }, + {SH_SANDY_FESTIVAL_OF_KI_SUL ,"SH_SANDY_FESTIVAL_OF_KI_SUL" }, + {SH_KI_SUL_RAMPAGE ,"SH_KI_SUL_RAMPAGE" }, + {SH_COMMUNE_WITH_HYUN_ROK ,"SH_COMMUNE_WITH_HYUN_ROK" }, + {SH_COLORS_OF_HYUN_ROK ,"SH_COLORS_OF_HYUN_ROK" }, + {SH_HYUN_ROKS_BREEZE ,"SH_HYUN_ROKS_BREEZE" }, + {SH_HYUN_ROK_CANNON ,"SH_HYUN_ROK_CANNON" }, + {SH_TEMPORARY_COMMUNION ,"SH_TEMPORARY_COMMUNION" }, + {SH_BLESSING_OF_MYSTICAL_CREATURES ,"SH_BLESSING_OF_MYSTICAL_CREATURES" }, + + //Hypernovice skills + {HN_SELFSTUDY_TATICS ,"HN_SELFSTUDY_TATICS" }, + {HN_SELFSTUDY_SOCERY ,"HN_SELFSTUDY_SOCERY" }, + {HN_DOUBLEBOWLINGBASH ,"HN_DOUBLEBOWLINGBASH" }, + {HN_MEGA_SONIC_BLOW ,"HN_MEGA_SONIC_BLOW" }, + {HN_SHIELD_CHAIN_RUSH ,"HN_SHIELD_CHAIN_RUSH" }, + {HN_SPIRAL_PIERCE_MAX ,"HN_SPIRAL_PIERCE_MAX" }, + {HN_METEOR_STORM_BUSTER ,"HN_METEOR_STORM_BUSTER" }, + {HN_JUPITEL_THUNDER_STORM ,"HN_JUPITEL_THUNDER_STORM" }, + {HN_JACK_FROST_NOVA ,"HN_JACK_FROST_NOVA" }, + {HN_HELLS_DRIVE ,"HN_HELLS_DRIVE" }, + {HN_GROUND_GRAVITATION ,"HN_GROUND_GRAVITATION" }, + {HN_NAPALM_VULCAN_STRIKE ,"HN_NAPALM_VULCAN_STRIKE" }, + {HN_BREAKINGLIMIT ,"HN_BREAKINGLIMIT" }, + {HN_RULEBREAK ,"HN_RULEBREAK" }, + + //Sky Emperor skills + {SKE_SKY_MASTERY ,"SKE_SKY_MASTERY" }, + {SKE_WAR_BOOK_MASTERY ,"SKE_WAR_BOOK_MASTERY" }, + {SKE_RISING_SUN ,"SKE_RISING_SUN" }, + {SKE_NOON_BLAST ,"SKE_NOON_BLAST" }, + {SKE_SUNSET_BLAST ,"SKE_SUNSET_BLAST" }, + {SKE_RISING_MOON ,"SKE_RISING_MOON" }, + {SKE_MIDNIGHT_KICK ,"SKE_MIDNIGHT_KICK" }, + {SKE_DAWN_BREAK ,"SKE_DAWN_BREAK" }, + {SKE_TWINKLING_GALAXY ,"SKE_TWINKLING_GALAXY" }, + {SKE_STAR_BURST ,"SKE_STAR_BURST" }, + {SKE_STAR_CANNON ,"SKE_STAR_CANNON" }, + {SKE_ALL_IN_THE_SKY ,"SKE_ALL_IN_THE_SKY" }, + {SKE_ENCHANTING_SKY ,"SKE_ENCHANTING_SKY" }, + + //Shinkiro / Shiranui skills + {SS_TOKEDASU ,"SS_TOKEDASU" }, + {SS_SHIMIRU ,"SS_SHIMIRU" }, + {SS_AKUMUKESU ,"SS_AKUMUKESU" }, + {SS_SHINKIROU ,"SS_SHINKIROU" }, + {SS_KAGEGARI ,"SS_KAGEGARI" }, + {SS_KAGENOMAI ,"SS_KAGENOMAI" }, + {SS_KAGEGISSEN ,"SS_KAGEGISSEN" }, + {SS_FUUMASHOUAKU ,"SS_FUUMASHOUAKU" }, + {SS_FUUMAKOUCHIKU ,"SS_FUUMAKOUCHIKU" }, + {SS_KUNAIWAIKYOKU ,"SS_KUNAIWAIKYOKU" }, + {SS_KUNAIKAITEN ,"SS_KUNAIKAITEN" }, + {SS_KUNAIKUSSETSU ,"SS_KUNAIKUSSETSU" }, + {SS_SEKIENHOU ,"SS_SEKIENHOU" }, + {SS_REIKETSUHOU ,"SS_REIKETSUHOU" }, + {SS_RAIDENPOU ,"SS_RAIDENPOU" }, + {SS_KINRYUUHOU ,"SS_KINRYUUHOU" }, + {SS_ANTENPOU ,"SS_ANTENPOU" }, + {SS_KAGEAKUMU ,"SS_KAGEAKUMU" }, + {SS_HITOUAKUMU ,"SS_HITOUAKUMU" }, + {SS_ANKOKURYUUAKUMU ,"SS_ANKOKURYUUAKUMU" }, + + //Additional quest skills + {NW_THE_VIGILANTE_AT_NIGHT_GUN_GATLING ,"NW_THE_VIGILANTE_AT_NIGHT_GUN_GATLING" }, + {NW_THE_VIGILANTE_AT_NIGHT_GUN_SHOTGUN ,"NW_THE_VIGILANTE_AT_NIGHT_GUN_SHOTGUN" }, + {SS_FUUMAKOUCHIKU_BLASTING ,"SS_FUUMAKOUCHIKU_BLASTING" }, + {DK_DRAGONIC_BREATH ,"DK_DRAGONIC_BREATH" }, + {MT_SPARK_BLASTER ,"MT_SPARK_BLASTER" }, + {MT_TRIPLE_LASER ,"MT_TRIPLE_LASER" }, + {MT_MIGHTY_SMASH ,"MT_MIGHTY_SMASH" }, + {BO_EXPLOSIVE_POWDER ,"BO_EXPLOSIVE_POWDER" }, + {BO_MAYHEMIC_THORNS ,"BO_MAYHEMIC_THORNS" }, + + //Additional misc skills + {HLIF_HEAL ,"HLIF_HEAL" }, + {HLIF_AVOID ,"HLIF_AVOID" }, + {HLIF_BRAIN ,"HLIF_BRAIN" }, + {HLIF_CHANGE ,"HLIF_CHANGE" }, + {HAMI_CASTLE ,"HAMI_CASTLE" }, + {HAMI_DEFENCE ,"HAMI_DEFENCE" }, + {HAMI_SKIN ,"HAMI_SKIN" }, + {HAMI_BLOODLUST ,"HAMI_BLOODLUST" }, + {HFLI_MOON ,"HFLI_MOON" }, + {HFLI_FLEET ,"HFLI_FLEET" }, + {HFLI_SPEED ,"HFLI_SPEED" }, + {HFLI_SBR44 ,"HFLI_SBR44" }, + {HVAN_CAPRICE ,"HVAN_CAPRICE" }, + {HVAN_CHAOTIC ,"HVAN_CHAOTIC" }, + {HVAN_INSTRUCT ,"HVAN_INSTRUCT" }, + {HVAN_EXPLOSION ,"HVAN_EXPLOSION" }, + {MUTATION_BASEJOB ,"MUTATION_BASEJOB" }, + {MH_SUMMON_LEGION ,"MH_SUMMON_LEGION" }, + {MH_NEEDLE_OF_PARALYZE ,"MH_NEEDLE_OF_PARALYZE" }, + {MH_POISON_MIST ,"MH_POISON_MIST" }, + {MH_PAIN_KILLER ,"MH_PAIN_KILLER" }, + {MH_LIGHT_OF_REGENE ,"MH_LIGHT_OF_REGENE" }, + {MH_OVERED_BOOST ,"MH_OVERED_BOOST" }, + {MH_ERASER_CUTTER ,"MH_ERASER_CUTTER" }, + {MH_XENO_SLASHER ,"MH_XENO_SLASHER" }, + {MH_SILENT_BREEZE ,"MH_SILENT_BREEZE" }, + {MH_STYLE_CHANGE ,"MH_STYLE_CHANGE" }, + {MH_SONIC_CRAW ,"MH_SONIC_CRAW" }, + {MH_SILVERVEIN_RUSH ,"MH_SILVERVEIN_RUSH" }, + {MH_MIDNIGHT_FRENZY ,"MH_MIDNIGHT_FRENZY" }, + {MH_STAHL_HORN ,"MH_STAHL_HORN" }, + {MH_GOLDENE_FERSE ,"MH_GOLDENE_FERSE" }, + {MH_STEINWAND ,"MH_STEINWAND" }, + {MH_HEILIGE_STANGE ,"MH_HEILIGE_STANGE" }, + {MH_ANGRIFFS_MODUS ,"MH_ANGRIFFS_MODUS" }, + {MH_TINDER_BREAKER ,"MH_TINDER_BREAKER" }, + {MH_CBC ,"MH_CBC" }, + {MH_EQC ,"MH_EQC" }, + {MH_MAGMA_FLOW ,"MH_MAGMA_FLOW" }, + {MH_GRANITIC_ARMOR ,"MH_GRANITIC_ARMOR" }, + {MH_LAVA_SLIDE ,"MH_LAVA_SLIDE" }, + {MH_PYROCLASTIC ,"MH_PYROCLASTIC" }, + {MH_VOLCANIC_ASH ,"MH_VOLCANIC_ASH" }, + {MH_BLAST_FORGE ,"MH_BLAST_FORGE" }, + {MH_TEMPERING ,"MH_TEMPERING" }, + {MH_CLASSY_FLUTTER ,"MH_CLASSY_FLUTTER" }, + {MH_TWISTER_CUTTER ,"MH_TWISTER_CUTTER" }, + {MH_ABSOLUTE_ZEPHYR ,"MH_ABSOLUTE_ZEPHYR" }, + {MH_BRUSHUP_CLAW ,"MH_BRUSHUP_CLAW" }, + {MH_BLAZING_AND_FURIOUS ,"MH_BLAZING_AND_FURIOUS" }, + {MH_THE_ONE_FIGHTER_RISES ,"MH_THE_ONE_FIGHTER_RISES" }, + {MH_POLISHING_NEEDLE ,"MH_POLISHING_NEEDLE" }, + {MH_TOXIN_OF_MANDARA ,"MH_TOXIN_OF_MANDARA" }, + {MH_NEEDLE_STINGER ,"MH_NEEDLE_STINGER" }, + {MH_LICHT_GEHORN ,"MH_LICHT_GEHORN" }, + {MH_GLANZEN_SPIES ,"MH_GLANZEN_SPIES" }, + {MH_HEILIGE_PFERD ,"MH_HEILIGE_PFERD" }, + {MH_GOLDENE_TONE ,"MH_GOLDENE_TONE" }, + {MH_BLAZING_LAVA ,"MH_BLAZING_LAVA" }, + + {MS_BASH ,"MS_BASH" }, + {MS_MAGNUM ,"MS_MAGNUM" }, + {MS_BOWLINGBASH ,"MS_BOWLINGBASH" }, + {MS_PARRYING ,"MS_PARRYING" }, + {MS_REFLECTSHIELD ,"MS_REFLECTSHIELD" }, + {MS_BERSERK ,"MS_BERSERK" }, + {MA_DOUBLE ,"MA_DOUBLE" }, + {MA_SHOWER ,"MA_SHOWER" }, + {MA_SKIDTRAP ,"MA_SKIDTRAP" }, + {MA_LANDMINE ,"MA_LANDMINE" }, + {MA_SANDMAN ,"MA_SANDMAN" }, + {MA_FREEZINGTRAP ,"MA_FREEZINGTRAP" }, + {MA_REMOVETRAP ,"MA_REMOVETRAP" }, + {MA_CHARGEARROW ,"MA_CHARGEARROW" }, + {MA_SHARPSHOOTING ,"MA_SHARPSHOOTING" }, + {ML_PIERCE ,"ML_PIERCE" }, + {ML_BRANDISH ,"ML_BRANDISH" }, + {ML_SPIRALPIERCE ,"ML_SPIRALPIERCE" }, + {ML_DEFENDER ,"ML_DEFENDER" }, + {ML_AUTOGUARD ,"ML_AUTOGUARD" }, + {ML_DEVOTION ,"ML_DEVOTION" }, + {MER_MAGNIFICAT ,"MER_MAGNIFICAT" }, + {MER_QUICKEN ,"MER_QUICKEN" }, + {MER_SIGHT ,"MER_SIGHT" }, + {MER_CRASH ,"MER_CRASH" }, + {MER_REGAIN ,"MER_REGAIN" }, + {MER_TENDER ,"MER_TENDER" }, + {MER_BENEDICTION ,"MER_BENEDICTION" }, + {MER_RECUPERATE ,"MER_RECUPERATE" }, + {MER_MENTALCURE ,"MER_MENTALCURE" }, + {MER_COMPRESS ,"MER_COMPRESS" }, + {MER_PROVOKE ,"MER_PROVOKE" }, + {MER_AUTOBERSERK ,"MER_AUTOBERSERK" }, + {MER_DECAGI ,"MER_DECAGI" }, + {MER_SCAPEGOAT ,"MER_SCAPEGOAT" }, + {MER_LEXDIVINA ,"MER_LEXDIVINA" }, + {MER_ESTIMATION ,"MER_ESTIMATION" }, + {MER_KYRIE ,"MER_KYRIE" }, + {MER_BLESSING ,"MER_BLESSING" }, + {MER_INCAGI ,"MER_INCAGI" }, + {MER_INVINCIBLEOFF2 ,"MER_INVINCIBLEOFF2" }, + + {EL_CIRCLE_OF_FIRE ,"EL_CIRCLE_OF_FIRE" }, + {EL_FIRE_CLOAK ,"EL_FIRE_CLOAK" }, + {EL_FIRE_MANTLE ,"EL_FIRE_MANTLE" }, + {EL_WATER_SCREEN ,"EL_WATER_SCREEN" }, + {EL_WATER_DROP ,"EL_WATER_DROP" }, + {EL_WATER_BARRIER ,"EL_WATER_BARRIER" }, + {EL_WIND_STEP ,"EL_WIND_STEP" }, + {EL_WIND_CURTAIN ,"EL_WIND_CURTAIN" }, + {EL_ZEPHYR ,"EL_ZEPHYR" }, + {EL_SOLID_SKIN ,"EL_SOLID_SKIN" }, + {EL_STONE_SHIELD ,"EL_STONE_SHIELD" }, + {EL_POWER_OF_GAIA ,"EL_POWER_OF_GAIA" }, + {EL_PYROTECHNIC ,"EL_PYROTECHNIC" }, + {EL_HEATER ,"EL_HEATER" }, + {EL_TROPIC ,"EL_TROPIC" }, + {EL_AQUAPLAY ,"EL_AQUAPLAY" }, + {EL_COOLER ,"EL_COOLER" }, + {EL_CHILLY_AIR ,"EL_CHILLY_AIR" }, + {EL_GUST ,"EL_GUST" }, + {EL_BLAST ,"EL_BLAST" }, + {EL_WILD_STORM ,"EL_WILD_STORM" }, + {EL_PETROLOGY ,"EL_PETROLOGY" }, + {EL_CURSED_SOIL ,"EL_CURSED_SOIL" }, + {EL_UPHEAVAL ,"EL_UPHEAVAL" }, + {EL_FIRE_ARROW ,"EL_FIRE_ARROW" }, + {EL_FIRE_BOMB ,"EL_FIRE_BOMB" }, + {EL_FIRE_BOMB_ATK ,"EL_FIRE_BOMB_ATK" }, + {EL_FIRE_WAVE ,"EL_FIRE_WAVE" }, + {EL_FIRE_WAVE_ATK ,"EL_FIRE_WAVE_ATK" }, + {EL_ICE_NEEDLE ,"EL_ICE_NEEDLE" }, + {EL_WATER_SCREW ,"EL_WATER_SCREW" }, + {EL_WATER_SCREW_ATK ,"EL_WATER_SCREW_ATK" }, + {EL_TIDAL_WEAPON ,"EL_TIDAL_WEAPON" }, + {EL_WIND_SLASH ,"EL_WIND_SLASH" }, + {EL_HURRICANE ,"EL_HURRICANE" }, + {EL_HURRICANE_ATK ,"EL_HURRICANE_ATK" }, + {EL_TYPOON_MIS ,"EL_TYPOON_MIS" }, + {EL_TYPOON_MIS_ATK ,"EL_TYPOON_MIS_ATK" }, + {EL_STONE_HAMMER ,"EL_STONE_HAMMER" }, + {EL_ROCK_CRUSHER ,"EL_ROCK_CRUSHER" }, + {EL_ROCK_CRUSHER_ATK ,"EL_ROCK_CRUSHER_ATK" }, + {EL_STONE_RAIN ,"EL_STONE_RAIN" }, + {EM_EL_FLAMETECHNIC ,"EM_EL_FLAMETECHNIC" }, + {EM_EL_FLAMEARMOR ,"EM_EL_FLAMEARMOR" }, + {EM_EL_FLAMEROCK ,"EM_EL_FLAMEROCK" }, + {EM_EL_COLD_FORCE ,"EM_EL_COLD_FORCE" }, + {EM_EL_CRYSTAL_ARMOR ,"EM_EL_CRYSTAL_ARMOR" }, + {EM_EL_AGE_OF_ICE ,"EM_EL_AGE_OF_ICE" }, + {EM_EL_GRACE_BREEZE ,"EM_EL_GRACE_BREEZE" }, + {EM_EL_EYES_OF_STORM ,"EM_EL_EYES_OF_STORM" }, + {EM_EL_STORM_WIND ,"EM_EL_STORM_WIND" }, + {EM_EL_EARTH_CARE ,"EM_EL_EARTH_CARE" }, + {EM_EL_STRONG_PROTECTION ,"EM_EL_STRONG_PROTECTION" }, + {EM_EL_AVALANCHE ,"EM_EL_AVALANCHE" }, + {EM_EL_DEEP_POISONING ,"EM_EL_DEEP_POISONING" }, + {EM_EL_POISON_SHIELD ,"EM_EL_POISON_SHIELD" }, + {EM_EL_DEADLY_POISON ,"EM_EL_DEADLY_POISON" }, + + //Additional Meister skills + {ABR_BATTLE_BUSTER ,"ABR_BATTLE_BUSTER" }, + {ABR_DUAL_CANNON_FIRE ,"ABR_DUAL_CANNON_FIRE" }, + {ABR_NET_REPAIR ,"ABR_NET_REPAIR" }, + {ABR_NET_SUPPORT ,"ABR_NET_SUPPORT" }, + {ABR_INFINITY_BUSTER ,"ABR_INFINITY_BUSTER" } + }; + + // Look up the skill_id in the map + int skill = (int)skill_id; + + auto it = skill_map.find(skill); + if (it != skill_map.end()) { + return it->second.c_str(); // Return the corresponding str value + } + else { + ShowError("Unknown skill ID: %s\n", skill_id); + return "UNKNOWN"; // Return an error value if the skill ID is unknown + } + + +} diff --git a/src/bot/Brokk/helper_skills.hpp b/src/bot/Brokk/helper_skills.hpp new file mode 100644 index 0000000..9910884 --- /dev/null +++ b/src/bot/Brokk/helper_skills.hpp @@ -0,0 +1,1950 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef HELPER_SKILLS_HPP +#define HELPER_SKILLS_HPP + +#include +//#include // ACCOUNT_REG2_NUM, WEB_AUTH_TOKEN_LENGTH +#include + +typedef struct SkillDB SkillDB; +typedef struct SkillDBIterator SkillDBIterator; + + +// standard engines +SkillDB* skill_db_sql(void); + +struct bot_skill { + uint32 char_id; // ID of corresponding char account + int skill_id; // ID of skill + uint8 level; // Learned level of skill + int flag; // Name of the player character that spawned/summoned the bot [Default: Brokk for generated bots) +}; + + +struct SkillDBIterator { + /// Destroys this iterator, releasing all allocated memory (including itself). + /// + /// @param self Iterator + void (*destroy)(SkillDBIterator* self); + + /// Fetches the next skill in the database. + /// Fills bot_skill with the skill data. + /// @param self Iterator + /// @param skill Skill data + /// @return true if successful + bool (*next)(SkillDBIterator* self, const uint32 char_id, struct bot_skill* skill); +}; + + +struct SkillDB { + /// Initializes this database, making it ready for use. + /// Call this after setting the properties. + /// + /// @param self Database + /// @return true if successful + bool (*init)(SkillDB* self); + + /// Destroys this database, releasing all allocated memory (including itself). + /// + /// @param self Database + void (*destroy)(SkillDB* self); + + /// Gets a property from this database. + /// These read-only properties must be implemented: + /// + /// @param self Database + /// @param key Property name + /// @param buf Buffer for the value + /// @param buflen Buffer length + /// @return true if successful + bool (*get_property)(SkillDB* 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)(SkillDB* self, const char* key, const char* value); + + /// Creates a new account in this database. + /// bot_acc->account_id must always be set, because there will never be a valid bot_account without login-table account + /// + /// @param self Database + /// @param skill bot_skill data + /// @return true if successful + bool (*create)(SkillDB* self, struct bot_skill* skill); + + /// Removes an skill entry from this database. + /// + /// @param self Database + /// @param char_id character id + /// @param skill_id id of skill + /// @return true if successful + bool (*remove)(SkillDB* self, const uint32 char_id, const uint32 skill_id); + + /// Modifies the data of an existing skill entry. + /// Uses bot_skill->char_id and bot_skill->skill_id to identify the entry. + /// + /// @param self Database + /// @param bot_skill Skill data + /// @return true if successful + bool (*save)(SkillDB* self, const struct bot_skill* skill); + + /// Finds an skill entry with char_id and skill_id and copies it to bot_skill. + /// + /// @param self Database + /// @param bot_skill Pointer that receives the skill data + /// @param char_id Target char id + /// @param skill_id Target skill id + /// @return true if successful + bool (*load_num)(SkillDB* self, struct bot_skill* skill, const uint32 char_id, const uint32 skill_id); + + /// Returns a new forward iterator. + /// + /// @param self Database + /// @return Iterator + SkillDBIterator* (*iterator)(SkillDB* self, const uint32 char_id); +}; + +int convert_skill_str_to_id(const char* skill_str); +const char* convert_skill_id_to_str(int skill_id); +//TODO Skill Tree handling hier auch einfügen + + +/// List of Skills +/* +enum e_bot_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, // Removed on kRO (renewal) + 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, + NPC_ICEMINE, + NPC_ICEEXPLO, + NPC_FLAMECROSS, + NPC_PULSESTRIKE2, + NPC_DANCINGBLADE, + NPC_DANCINGBLADE_ATK, + NPC_DARKPIERCING, + NPC_MAXPAIN, + NPC_MAXPAIN_ATK, + NPC_DEATHSUMMON, + NPC_HELLBURNING, + NPC_JACKFROST, + NPC_WIDEWEB, + NPC_WIDESUCK, + NPC_STORMGUST2, + NPC_FIRESTORM, + NPC_REVERBERATION, + NPC_REVERBERATION_ATK, + NPC_LEX_AETERNA, + NPC_ARROWSTORM, + NPC_CHEAL, + NPC_SR_CURSEDCIRCLE, + NPC_DRAGONBREATH, + NPC_FATALMENACE, + NPC_MAGMA_ERUPTION, + NPC_MAGMA_ERUPTION_DOTDAMAGE, + NPC_MANDRAGORA, + NPC_PSYCHIC_WAVE, + NPC_RAYOFGENESIS, + NPC_VENOMIMPRESS, + NPC_CLOUD_KILL, + NPC_IGNITIONBREAK, + NPC_PHANTOMTHRUST, + NPC_POISON_BUSTER, + NPC_HALLUCINATIONWALK, + NPC_ELECTRICWALK, + NPC_FIREWALK, + NPC_WIDEDISPEL, + NPC_LEASH, + NPC_WIDELEASH, + NPC_WIDECRITICALWOUND, + NPC_EARTHQUAKE_K, + NPC_ALL_STAT_DOWN, + NPC_GRADUAL_GRAVITY, + NPC_DAMAGE_HEAL, + NPC_IMMUNE_PROPERTY, + NPC_MOVE_COORDINATE, + NPC_WIDEBLEEDING2, + NPC_WIDESILENCE2, + NPC_WIDESTUN2, + NPC_WIDESTONE2, + NPC_WIDESLEEP2, + NPC_WIDECURSE2, + NPC_WIDECONFUSE2, + NPC_WIDEFREEZE2, + NPC_BLEEDING2, + NPC_ICEBREATH2, + NPC_ACIDBREATH2, + NPC_EVILLAND2, + NPC_HELLJUDGEMENT2, + NPC_RAINOFMETEOR, + NPC_GROUNDDRIVE, + NPC_RELIEVE_ON, + NPC_RELIEVE_OFF, + NPC_LOCKON_LASER, + NPC_LOCKON_LASER_ATK, + NPC_SEEDTRAP, + NPC_DEADLYCURSE, + NPC_RANDOMBREAK, + NPC_STRIP_SHADOW, + NPC_DEADLYCURSE2, + NPC_CANE_OF_EVIL_EYE, + NPC_CURSE_OF_RED_CUBE, + NPC_CURSE_OF_BLUE_CUBE, + NPC_KILLING_AURA, // 783 + + 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, // Removed on kRO + 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, // Removed on kRO + WM_REVERBERATION_MAGIC, // Removed on kRO + WM_DOMINION_IMPULSE, // Removed on kRO + 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, // Removed on kRO + GN_CHANGEMATERIAL, + GN_MIX_COOKING, + GN_MAKEBOMB, // Removed on kRO + GN_S_PHARMACY, + GN_SLINGITEM_RANGEMELEEATK, // Removed on kRO + + 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, + ALL_BEER_BOTTLE_CAP, + NPC_ASSASSINCROSS, + NPC_DISSONANCE, + NPC_UGLYDANCE, + ALL_TETANY, + ALL_RAY_OF_PROTECTION, + MC_CARTDECORATE, + GM_ITEM_ATKMAX, + GM_ITEM_ATKMIN, + GM_ITEM_MATKMAX, + GM_ITEM_MATKMIN, + + RL_GLITTERING_GREED = 2551, + RL_RICHS_COIN, + RL_MASS_SPIRAL, + RL_BANISHING_BUSTER, + RL_B_TRAP, + RL_FLICKER, + RL_S_STORM, + RL_E_CHAIN, + RL_QD_SHOT, + RL_C_MARKER, + RL_FIREDANCE, + RL_H_MINE, + RL_P_ALTER, + RL_FALLEN_ANGEL, + RL_R_TRIP, + RL_D_TAIL, + RL_FIRE_RAIN, + RL_HEAT_BARREL, + RL_AM_BLAST, + RL_SLUGSHOT, + RL_HAMMER_OF_GOD, + RL_R_TRIP_PLUSATK, + RL_B_FLICKER_ATK, + // RL_GLITTERING_GREED_ATK, + SJ_LIGHTOFMOON, + SJ_LUNARSTANCE, + SJ_FULLMOONKICK, + SJ_LIGHTOFSTAR, + SJ_STARSTANCE, + SJ_NEWMOONKICK, + SJ_FLASHKICK, + SJ_STAREMPEROR, + SJ_NOVAEXPLOSING, + SJ_UNIVERSESTANCE, + SJ_FALLINGSTAR, + SJ_GRAVITYCONTROL, + SJ_BOOKOFDIMENSION, + SJ_BOOKOFCREATINGSTAR, + SJ_DOCUMENT, + SJ_PURIFY, + SJ_LIGHTOFSUN, + SJ_SUNSTANCE, + SJ_SOLARBURST, + SJ_PROMINENCEKICK, + SJ_FALLINGSTAR_ATK, + SJ_FALLINGSTAR_ATK2, + SP_SOULGOLEM, + SP_SOULSHADOW, + SP_SOULFALCON, + SP_SOULFAIRY, + SP_CURSEEXPLOSION, + SP_SOULCURSE, + SP_SPA, + SP_SHA, + SP_SWHOO, + SP_SOULUNITY, + SP_SOULDIVISION, + SP_SOULREAPER, + SP_SOULREVOLVE, + SP_SOULCOLLECT, + SP_SOULEXPLOSION, + SP_SOULENERGY, + SP_KAUTE, + + 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, + + ALL_PRONTERA_RECALL = 3042, + ALL_GLASTHEIM_RECALL, + ALL_THANATOS_RECALL, + ALL_LIGHTHALZEN_RECALL, + + GC_DARKCROW = 5001, + RA_UNLIMIT, + GN_ILLUSIONDOPING, + RK_DRAGONBREATH_WATER, + RK_LUXANIMA, + NC_MAGMA_ERUPTION, + WM_FRIGG_SONG, + SO_ELEMENTAL_SHIELD, + SR_FLASHCOMBO, + SC_ESCAPE, + AB_OFFERTORIUM, + WL_TELEKINESIS_INTENSE, + LG_KINGS_GRACE, + ALL_FULL_THROTTLE, + NC_MAGMA_ERUPTION_DOTDAMAGE, + + SU_BASIC_SKILL = 5018, + SU_BITE, + SU_HIDE, + SU_SCRATCH, + SU_STOOP, + SU_LOPE, + SU_SPRITEMABLE, + SU_POWEROFLAND, + SU_SV_STEMSPEAR, + SU_CN_POWDERING, + SU_CN_METEOR, + SU_SV_ROOTTWIST, + SU_SV_ROOTTWIST_ATK, + SU_POWEROFLIFE, + SU_SCAROFTAROU, + SU_PICKYPECK, + SU_PICKYPECK_DOUBLE_ATK, + SU_ARCLOUSEDASH, + SU_LUNATICCARROTBEAT, + SU_POWEROFSEA, + SU_TUNABELLY, + SU_TUNAPARTY, + SU_BUNCHOFSHRIMP, + SU_FRESHSHRIMP, + SU_CN_METEOR2, + SU_LUNATICCARROTBEAT2, + SU_SOULATTACK, + SU_POWEROFFLOCK, + SU_SVG_SPIRIT, + SU_HISS, + SU_NYANGGRASS, + SU_GROOMING, + SU_PURRING, + SU_SHRIMPARTY, + SU_SPIRITOFLIFE, + SU_MEOWMEOW, + SU_SPIRITOFLAND, + SU_CHATTERING, + SU_SPIRITOFSEA, + + WE_CALLALLFAMILY = 5063, + WE_ONEFOREVER, + WE_CHEERUP, + + ALL_EQSWITCH = 5067, + + CG_SPECIALSINGER, + + AB_VITUPERATUM = 5072, + AB_CONVENIO, + ALL_LIGHTNING_STORM, + NV_BREAKTHROUGH, + NV_HELPANGEL, + NV_TRANSCENDENCE, + WL_READING_SB_READING, + + DK_SERVANTWEAPON = 5201, + DK_SERVANTWEAPON_ATK, + DK_SERVANT_W_SIGN, + DK_SERVANT_W_PHANTOM, + DK_SERVANT_W_DEMOL, + DK_CHARGINGPIERCE, + DK_TWOHANDDEF, + DK_HACKANDSLASHER, + DK_HACKANDSLASHER_ATK, + DK_DRAGONIC_AURA, + DK_MADNESS_CRUSHER, + DK_VIGOR, + DK_STORMSLASH, + + AG_DEADLY_PROJECTION, + AG_DESTRUCTIVE_HURRICANE, + AG_RAIN_OF_CRYSTAL, + AG_MYSTERY_ILLUSION, + AG_VIOLENT_QUAKE, + AG_VIOLENT_QUAKE_ATK, + AG_SOUL_VC_STRIKE, + AG_STRANTUM_TREMOR, + AG_ALL_BLOOM, + AG_ALL_BLOOM_ATK, + AG_ALL_BLOOM_ATK2, + AG_CRYSTAL_IMPACT, + AG_CRYSTAL_IMPACT_ATK, + AG_TORNADO_STORM, + AG_TWOHANDSTAFF, + AG_FLORAL_FLARE_ROAD, + AG_ASTRAL_STRIKE, + AG_ASTRAL_STRIKE_ATK, + AG_CLIMAX, + AG_ROCK_DOWN, + AG_STORM_CANNON, + AG_CRIMSON_ARROW, + AG_CRIMSON_ARROW_ATK, + AG_FROZEN_SLASH, + + IQ_POWERFUL_FAITH, + IQ_FIRM_FAITH, + IQ_WILL_OF_FAITH, + IQ_OLEUM_SANCTUM, + IQ_SINCERE_FAITH, + IQ_MASSIVE_F_BLASTER, + IQ_EXPOSION_BLASTER, + IQ_FIRST_BRAND, + IQ_FIRST_FAITH_POWER, + IQ_JUDGE, + IQ_SECOND_FLAME, + IQ_SECOND_FAITH, + IQ_SECOND_JUDGEMENT, + IQ_THIRD_PUNISH, + IQ_THIRD_FLAME_BOMB, + IQ_THIRD_CONSECRATION, + IQ_THIRD_EXOR_FLAME, + + IG_GUARD_STANCE, + IG_GUARDIAN_SHIELD, + IG_REBOUND_SHIELD, + IG_SHIELD_MASTERY, + IG_SPEAR_SWORD_M, + IG_ATTACK_STANCE, + IG_ULTIMATE_SACRIFICE, + IG_HOLY_SHIELD, + IG_GRAND_JUDGEMENT, + IG_JUDGEMENT_CROSS, + IG_SHIELD_SHOOTING, + IG_OVERSLASH, + IG_CROSS_RAIN, + + CD_REPARATIO, + CD_MEDIALE_VOTUM, + CD_MACE_BOOK_M, + CD_ARGUTUS_VITA, + CD_ARGUTUS_TELUM, + CD_ARBITRIUM, + CD_ARBITRIUM_ATK, + CD_PRESENS_ACIES, + CD_FIDUS_ANIMUS, + CD_EFFLIGO, + CD_COMPETENTIA, + CD_PNEUMATICUS_PROCELLA, + CD_DILECTIO_HEAL, + CD_RELIGIO, + CD_BENEDICTUM, + CD_PETITIO, + CD_FRAMEN, + + SHC_SHADOW_EXCEED, + SHC_DANCING_KNIFE, + SHC_SAVAGE_IMPACT, + SHC_SHADOW_SENSE, + SHC_ETERNAL_SLASH, + SHC_POTENT_VENOM, + SHC_SHADOW_STAB, + SHC_IMPACT_CRATER, + SHC_ENCHANTING_SHADOW, + SHC_FATAL_SHADOW_CROW, + + MT_AXE_STOMP, + MT_RUSH_QUAKE, + MT_M_MACHINE, + MT_A_MACHINE, + MT_D_MACHINE, + MT_TWOAXEDEF, + MT_ABR_M, + MT_SUMMON_ABR_BATTLE_WARIOR, + MT_SUMMON_ABR_DUAL_CANNON, + MT_SUMMON_ABR_MOTHER_NET, + MT_SUMMON_ABR_INFINITY, + + AG_DESTRUCTIVE_HURRICANE_CLIMAX, + BO_ACIDIFIED_ZONE_WATER_ATK, + BO_ACIDIFIED_ZONE_GROUND_ATK, + BO_ACIDIFIED_ZONE_WIND_ATK, + BO_ACIDIFIED_ZONE_FIRE_ATK, + + ABC_DAGGER_AND_BOW_M, + ABC_MAGIC_SWORD_M, + ABC_STRIP_SHADOW, + ABC_ABYSS_DAGGER, + ABC_UNLUCKY_RUSH, + ABC_CHAIN_REACTION_SHOT, + ABC_FROM_THE_ABYSS, + ABC_ABYSS_SLAYER, + ABC_ABYSS_STRIKE, + ABC_DEFT_STAB, + ABC_ABYSS_SQUARE, + ABC_FRENZY_SHOT, + + WH_ADVANCED_TRAP, + WH_WIND_SIGN, + WH_NATUREFRIENDLY, + WH_HAWKRUSH, + WH_HAWK_M, + WH_CALAMITYGALE, + WH_HAWKBOOMERANG, + WH_GALESTORM, + WH_DEEPBLINDTRAP, + WH_SOLIDTRAP, + WH_SWIFTTRAP, + WH_CRESCIVE_BOLT, + WH_FLAMETRAP, + + BO_BIONIC_PHARMACY, + BO_BIONICS_M, + BO_THE_WHOLE_PROTECTION, + BO_ADVANCE_PROTECTION, + BO_ACIDIFIED_ZONE_WATER, + BO_ACIDIFIED_ZONE_GROUND, + BO_ACIDIFIED_ZONE_WIND, + BO_ACIDIFIED_ZONE_FIRE, + BO_WOODENWARRIOR, + BO_WOODEN_FAIRY, + BO_CREEPER, + BO_RESEARCHREPORT, + BO_HELLTREE, + + TR_STAGE_MANNER, + TR_RETROSPECTION, + TR_MYSTIC_SYMPHONY, + TR_KVASIR_SONATA, + TR_ROSEBLOSSOM, + TR_ROSEBLOSSOM_ATK, + TR_RHYTHMSHOOTING, + TR_METALIC_FURY, + TR_SOUNDBLEND, + TR_GEF_NOCTURN, + TR_ROKI_CAPRICCIO, + TR_AIN_RHAPSODY, + TR_MUSICAL_INTERLUDE, + TR_JAWAII_SERENADE, + TR_NIPELHEIM_REQUIEM, + TR_PRON_MARCH, + + EM_MAGIC_BOOK_M, + EM_SPELL_ENCHANTING, + EM_ACTIVITY_BURN, + EM_INCREASING_ACTIVITY, + EM_DIAMOND_STORM, + EM_LIGHTNING_LAND, + EM_VENOM_SWAMP, + EM_CONFLAGRATION, + EM_TERRA_DRIVE, + EM_ELEMENTAL_SPIRIT_M, + EM_SUMMON_ELEMENTAL_ARDOR, + EM_SUMMON_ELEMENTAL_DILUVIO, + EM_SUMMON_ELEMENTAL_PROCELLA, + EM_SUMMON_ELEMENTAL_TERREMOTUS, + EM_SUMMON_ELEMENTAL_SERPENS, + EM_ELEMENTAL_BUSTER, + EM_ELEMENTAL_VEIL, + + ABC_CHAIN_REACTION_SHOT_ATK, + ABC_FROM_THE_ABYSS_ATK, + BO_WOODEN_THROWROCK, + BO_WOODEN_ATTACK, + BO_HELL_HOWLING, + BO_HELL_DUSTY, + BO_FAIRY_DUSTY, + EM_ELEMENTAL_BUSTER_FIRE, + EM_ELEMENTAL_BUSTER_WATER, + EM_ELEMENTAL_BUSTER_WIND, + EM_ELEMENTAL_BUSTER_GROUND, + EM_ELEMENTAL_BUSTER_POISON, + + NW_P_F_I = 5401, + NW_GRENADE_MASTERY, + NW_INTENSIVE_AIM, + NW_GRENADE_FRAGMENT, + NW_THE_VIGILANTE_AT_NIGHT, + NW_ONLY_ONE_BULLET, + NW_SPIRAL_SHOOTING, + NW_MAGAZINE_FOR_ONE, + NW_WILD_FIRE, + NW_BASIC_GRENADE, + NW_HASTY_FIRE_IN_THE_HOLE, + NW_GRENADES_DROPPING, + NW_AUTO_FIRING_LAUNCHER, + NW_HIDDEN_CARD, + NW_MISSION_BOMBARD, + + SOA_TALISMAN_MASTERY, + SOA_SOUL_MASTERY, + SOA_TALISMAN_OF_PROTECTION, + SOA_TALISMAN_OF_WARRIOR, + SOA_TALISMAN_OF_MAGICIAN, + SOA_SOUL_GATHERING, + SOA_TOTEM_OF_TUTELARY, + SOA_TALISMAN_OF_FIVE_ELEMENTS, + SOA_TALISMAN_OF_SOUL_STEALING, + SOA_EXORCISM_OF_MALICIOUS_SOUL, + SOA_TALISMAN_OF_BLUE_DRAGON, + SOA_TALISMAN_OF_WHITE_TIGER, + SOA_TALISMAN_OF_RED_PHOENIX, + SOA_TALISMAN_OF_BLACK_TORTOISE, + SOA_TALISMAN_OF_FOUR_BEARING_GOD, + SOA_CIRCLE_OF_DIRECTIONS_AND_ELEMENTALS, + SOA_SOUL_OF_HEAVEN_AND_EARTH, + + SH_MYSTICAL_CREATURE_MASTERY, + SH_COMMUNE_WITH_CHUL_HO, + SH_CHUL_HO_SONIC_CLAW, + SH_HOWLING_OF_CHUL_HO, + SH_HOGOGONG_STRIKE, + SH_COMMUNE_WITH_KI_SUL, + SH_KI_SUL_WATER_SPRAYING, + SH_MARINE_FESTIVAL_OF_KI_SUL, + SH_SANDY_FESTIVAL_OF_KI_SUL, + SH_KI_SUL_RAMPAGE, + SH_COMMUNE_WITH_HYUN_ROK, + SH_COLORS_OF_HYUN_ROK, + SH_HYUN_ROKS_BREEZE, + SH_HYUN_ROK_CANNON, + SH_TEMPORARY_COMMUNION, + SH_BLESSING_OF_MYSTICAL_CREATURES, + + HN_SELFSTUDY_TATICS, + HN_SELFSTUDY_SOCERY, + HN_DOUBLEBOWLINGBASH, + HN_MEGA_SONIC_BLOW, + HN_SHIELD_CHAIN_RUSH, + HN_SPIRAL_PIERCE_MAX, + HN_METEOR_STORM_BUSTER, + HN_JUPITEL_THUNDER_STORM, + HN_JACK_FROST_NOVA, + HN_HELLS_DRIVE, + HN_GROUND_GRAVITATION, + HN_NAPALM_VULCAN_STRIKE, + HN_BREAKINGLIMIT, + HN_RULEBREAK, + + SKE_SKY_MASTERY, + SKE_WAR_BOOK_MASTERY, + SKE_RISING_SUN, + SKE_NOON_BLAST, + SKE_SUNSET_BLAST, + SKE_RISING_MOON, + SKE_MIDNIGHT_KICK, + SKE_DAWN_BREAK, + SKE_TWINKLING_GALAXY, + SKE_STAR_BURST, + SKE_STAR_CANNON, + SKE_ALL_IN_THE_SKY, + SKE_ENCHANTING_SKY, + + SS_TOKEDASU, + SS_SHIMIRU, + SS_AKUMUKESU, + SS_SHINKIROU, + SS_KAGEGARI, + SS_KAGENOMAI, + SS_KAGEGISSEN, + SS_FUUMASHOUAKU, + SS_FUUMAKOUCHIKU, + SS_KUNAIWAIKYOKU, + SS_KUNAIKAITEN, + SS_KUNAIKUSSETSU, + SS_SEKIENHOU, + SS_REIKETSUHOU, + SS_RAIDENPOU, + SS_KINRYUUHOU, + SS_ANTENPOU, + SS_KAGEAKUMU, + SS_HITOUAKUMU, + SS_ANKOKURYUUAKUMU, + + NW_THE_VIGILANTE_AT_NIGHT_GUN_GATLING, + NW_THE_VIGILANTE_AT_NIGHT_GUN_SHOTGUN, + SS_FUUMAKOUCHIKU_BLASTING, + + DK_DRAGONIC_BREATH = 6001, + MT_SPARK_BLASTER, + MT_TRIPLE_LASER, + MT_MIGHTY_SMASH, + BO_EXPLOSIVE_POWDER, + BO_MAYHEMIC_THORNS, + + 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, + MH_BLAST_FORGE, + MH_TEMPERING, + MH_CLASSY_FLUTTER, + MH_TWISTER_CUTTER, + MH_ABSOLUTE_ZEPHYR, + MH_BRUSHUP_CLAW, + MH_BLAZING_AND_FURIOUS, + MH_THE_ONE_FIGHTER_RISES, + MH_POLISHING_NEEDLE, + MH_TOXIN_OF_MANDARA, + MH_NEEDLE_STINGER, + MH_LICHT_GEHORN, + MH_GLANZEN_SPIES, + MH_HEILIGE_PFERD, + MH_GOLDENE_TONE, + MH_BLAZING_LAVA, + + 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, + MER_INVINCIBLEOFF2, + + 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, + EM_EL_FLAMETECHNIC, + EM_EL_FLAMEARMOR, + EM_EL_FLAMEROCK, + EM_EL_COLD_FORCE, + EM_EL_CRYSTAL_ARMOR, + EM_EL_AGE_OF_ICE, + EM_EL_GRACE_BREEZE, + EM_EL_EYES_OF_STORM, + EM_EL_STORM_WIND, + EM_EL_EARTH_CARE, + EM_EL_STRONG_PROTECTION, + EM_EL_AVALANCHE, + EM_EL_DEEP_POISONING, + EM_EL_POISON_SHIELD, + EM_EL_DEADLY_POISON, + + ABR_BATTLE_BUSTER = 8601, + ABR_DUAL_CANNON_FIRE, + ABR_NET_REPAIR, + ABR_NET_SUPPORT, + ABR_INFINITY_BUSTER, +}; +*/ +#endif /* HELPER_SKILLS_HPP */ diff --git a/src/bot/Brokk/json.hpp b/src/bot/Brokk/json.hpp new file mode 100644 index 0000000..a858728 --- /dev/null +++ b/src/bot/Brokk/json.hpp @@ -0,0 +1,24766 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +/****************************************************************************\ + * Note on documentation: The source files contain links to the online * + * documentation of the public API at https://json.nlohmann.me. This URL * + * contains the most recent documentation and should also be applicable to * + * previous versions; documentation for deprecated functions is not * + * removed, but marked deprecated. See "Generate documentation" section in * + * file docs/README.md. * +\****************************************************************************/ + +#ifndef INCLUDE_NLOHMANN_JSON_HPP_ +#define INCLUDE_NLOHMANN_JSON_HPP_ + +#include // all_of, find, for_each +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#ifndef JSON_NO_IO + #include // istream, ostream +#endif // JSON_NO_IO +#include // random_access_iterator_tag +#include // unique_ptr +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // transform +#include // array +#include // forward_list +#include // inserter, front_inserter, end +#include // map +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // unordered_map +#include // pair, declval +#include // valarray + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // nullptr_t +#include // exception +#if JSON_DIAGNOSTICS + #include // accumulate +#endif +#include // runtime_error +#include // to_string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // uint8_t +#include // string + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // declval, pair +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template struct make_void +{ + using type = void; +}; +template using void_t = typename make_void::type; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +// https://en.cppreference.com/w/cpp/experimental/is_detected +struct nonesuch +{ + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; +}; + +template class Op, + class... Args> +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template class Op, class... Args> +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +template class Op, class... Args> +using is_detected = typename detector::value_t; + +template class Op, class... Args> +struct is_detected_lazy : is_detected { }; + +template class Op, class... Args> +using detected_t = typename detector::type; + +template class Op, class... Args> +using detected_or = detector; + +template class Op, class... Args> +using detected_or_t = typename detected_or::type; + +template class Op, class... Args> +using is_detected_exact = std::is_same>; + +template class Op, class... Args> +using is_detected_convertible = + std::is_convertible, To>; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + + +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +/* Hedley - https://nemequ.github.io/hedley + * Created by Evan Nemerson + */ + +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) +#if defined(JSON_HEDLEY_VERSION) + #undef JSON_HEDLEY_VERSION +#endif +#define JSON_HEDLEY_VERSION 15 + +#if defined(JSON_HEDLEY_STRINGIFY_EX) + #undef JSON_HEDLEY_STRINGIFY_EX +#endif +#define JSON_HEDLEY_STRINGIFY_EX(x) #x + +#if defined(JSON_HEDLEY_STRINGIFY) + #undef JSON_HEDLEY_STRINGIFY +#endif +#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) + +#if defined(JSON_HEDLEY_CONCAT_EX) + #undef JSON_HEDLEY_CONCAT_EX +#endif +#define JSON_HEDLEY_CONCAT_EX(a,b) a##b + +#if defined(JSON_HEDLEY_CONCAT) + #undef JSON_HEDLEY_CONCAT +#endif +#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) + +#if defined(JSON_HEDLEY_CONCAT3_EX) + #undef JSON_HEDLEY_CONCAT3_EX +#endif +#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c + +#if defined(JSON_HEDLEY_CONCAT3) + #undef JSON_HEDLEY_CONCAT3 +#endif +#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) + +#if defined(JSON_HEDLEY_VERSION_ENCODE) + #undef JSON_HEDLEY_VERSION_ENCODE +#endif +#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) + #undef JSON_HEDLEY_VERSION_DECODE_MAJOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) + #undef JSON_HEDLEY_VERSION_DECODE_MINOR +#endif +#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) + +#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) + #undef JSON_HEDLEY_VERSION_DECODE_REVISION +#endif +#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) + +#if defined(JSON_HEDLEY_GNUC_VERSION) + #undef JSON_HEDLEY_GNUC_VERSION +#endif +#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#elif defined(__GNUC__) + #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) +#endif + +#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) + #undef JSON_HEDLEY_GNUC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GNUC_VERSION) + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION) + #undef JSON_HEDLEY_MSVC_VERSION +#endif +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) +#elif defined(_MSC_VER) && !defined(__ICL) + #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) + #undef JSON_HEDLEY_MSVC_VERSION_CHECK +#endif +#if !defined(JSON_HEDLEY_MSVC_VERSION) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) +#elif defined(_MSC_VER) && (_MSC_VER >= 1400) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) +#else + #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION) + #undef JSON_HEDLEY_INTEL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) + #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_VERSION) + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION) + #undef JSON_HEDLEY_PGI_VERSION +#endif +#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) + #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) +#endif + +#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) + #undef JSON_HEDLEY_PGI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PGI_VERSION) + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #undef JSON_HEDLEY_SUNPRO_VERSION +#endif +#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) +#elif defined(__SUNPRO_C) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) +#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) +#elif defined(__SUNPRO_CC) + #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) +#endif + +#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) + #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_SUNPRO_VERSION) + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION +#endif +#if defined(__EMSCRIPTEN__) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) +#endif + +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) + #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION) + #undef JSON_HEDLEY_ARM_VERSION +#endif +#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) +#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) + #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) +#endif + +#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) + #undef JSON_HEDLEY_ARM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_ARM_VERSION) + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION) + #undef JSON_HEDLEY_IBM_VERSION +#endif +#if defined(__ibmxl__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) +#elif defined(__xlC__) && defined(__xlC_ver__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) +#elif defined(__xlC__) + #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) +#endif + +#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) + #undef JSON_HEDLEY_IBM_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IBM_VERSION) + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_VERSION) + #undef JSON_HEDLEY_TI_VERSION +#endif +#if \ + defined(__TI_COMPILER_VERSION__) && \ + ( \ + defined(__TMS470__) || defined(__TI_ARM__) || \ + defined(__MSP430__) || \ + defined(__TMS320C2000__) \ + ) +#if (__TI_COMPILER_VERSION__ >= 16000000) + #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif +#endif + +#if defined(JSON_HEDLEY_TI_VERSION_CHECK) + #undef JSON_HEDLEY_TI_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_VERSION) + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #undef JSON_HEDLEY_TI_CL2000_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) + #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL2000_VERSION) + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #undef JSON_HEDLEY_TI_CL430_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) + #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL430_VERSION) + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #undef JSON_HEDLEY_TI_ARMCL_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) + #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) + #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #undef JSON_HEDLEY_TI_CL6X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) + #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL6X_VERSION) + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #undef JSON_HEDLEY_TI_CL7X_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) + #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CL7X_VERSION) + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #undef JSON_HEDLEY_TI_CLPRU_VERSION +#endif +#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) + #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) +#endif + +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) + #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION) + #undef JSON_HEDLEY_CRAY_VERSION +#endif +#if defined(_CRAYC) + #if defined(_RELEASE_PATCHLEVEL) + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) + #else + #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) + #undef JSON_HEDLEY_CRAY_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_CRAY_VERSION) + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION) + #undef JSON_HEDLEY_IAR_VERSION +#endif +#if defined(__IAR_SYSTEMS_ICC__) + #if __VER__ > 1000 + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) + #else + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) + #endif +#endif + +#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) + #undef JSON_HEDLEY_IAR_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_IAR_VERSION) + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION) + #undef JSON_HEDLEY_TINYC_VERSION +#endif +#if defined(__TINYC__) + #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) +#endif + +#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) + #undef JSON_HEDLEY_TINYC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION) + #undef JSON_HEDLEY_DMC_VERSION +#endif +#if defined(__DMC__) + #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) +#endif + +#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) + #undef JSON_HEDLEY_DMC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_DMC_VERSION) + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #undef JSON_HEDLEY_COMPCERT_VERSION +#endif +#if defined(__COMPCERT_VERSION__) + #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) +#endif + +#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) + #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_COMPCERT_VERSION) + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION) + #undef JSON_HEDLEY_PELLES_VERSION +#endif +#if defined(__POCC__) + #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) +#endif + +#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) + #undef JSON_HEDLEY_PELLES_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_PELLES_VERSION) + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION) + #undef JSON_HEDLEY_GCC_VERSION +#endif +#if \ + defined(JSON_HEDLEY_GNUC_VERSION) && \ + !defined(__clang__) && \ + !defined(JSON_HEDLEY_INTEL_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ + !defined(JSON_HEDLEY_TI_VERSION) && \ + !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ + !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION +#endif + +#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_GCC_VERSION) + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_ATTRIBUTE +#endif +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#else +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE +#endif +#if defined(__has_attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE +#endif +#if \ + defined(__has_cpp_attribute) && \ + defined(__cplusplus) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) + #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS +#endif +#if !defined(__cplusplus) || !defined(__has_cpp_attribute) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#elif \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION) && \ + (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ + (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) +#else + #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE +#endif +#if defined(__has_cpp_attribute) && defined(__cplusplus) + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_BUILTIN) + #undef JSON_HEDLEY_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) +#else + #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) + #undef JSON_HEDLEY_GNUC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) + #undef JSON_HEDLEY_GCC_HAS_BUILTIN +#endif +#if defined(__has_builtin) + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) +#else + #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_FEATURE) + #undef JSON_HEDLEY_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) +#else + #define JSON_HEDLEY_HAS_FEATURE(feature) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) + #undef JSON_HEDLEY_GNUC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) + #undef JSON_HEDLEY_GCC_HAS_FEATURE +#endif +#if defined(__has_feature) + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) +#else + #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_EXTENSION) + #undef JSON_HEDLEY_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) +#else + #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) + #undef JSON_HEDLEY_GNUC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) + #undef JSON_HEDLEY_GCC_HAS_EXTENSION +#endif +#if defined(__has_extension) + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) +#else + #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE +#endif +#if defined(__has_declspec_attribute) + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) +#else + #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_HAS_WARNING) + #undef JSON_HEDLEY_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) +#else + #define JSON_HEDLEY_HAS_WARNING(warning) (0) +#endif + +#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) + #undef JSON_HEDLEY_GNUC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_GCC_HAS_WARNING) + #undef JSON_HEDLEY_GCC_HAS_WARNING +#endif +#if defined(__has_warning) + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) +#else + #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + +/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") +# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# endif +# endif +#endif +#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x +#endif + +#if defined(JSON_HEDLEY_CONST_CAST) + #undef JSON_HEDLEY_CONST_CAST +#endif +#if defined(__cplusplus) +# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) +#elif \ + JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_REINTERPRET_CAST) + #undef JSON_HEDLEY_REINTERPRET_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) +#else + #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_STATIC_CAST) + #undef JSON_HEDLEY_STATIC_CAST +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) +#else + #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) +#endif + +#if defined(JSON_HEDLEY_CPP_CAST) + #undef JSON_HEDLEY_CPP_CAST +#endif +#if defined(__cplusplus) +# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ + ((T) (expr)) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) +# define JSON_HEDLEY_CPP_CAST(T, expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("diag_suppress=Pe137") \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) +# endif +#else +# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") +#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + +#if defined(JSON_HEDLEY_DEPRECATED) + #undef JSON_HEDLEY_DEPRECATED +#endif +#if defined(JSON_HEDLEY_DEPRECATED_FOR) + #undef JSON_HEDLEY_DEPRECATED_FOR +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) +#elif \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") +#else + #define JSON_HEDLEY_DEPRECATED(since) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) +#endif + +#if defined(JSON_HEDLEY_UNAVAILABLE) + #undef JSON_HEDLEY_UNAVAILABLE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) +#else + #define JSON_HEDLEY_UNAVAILABLE(available_since) +#endif + +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT +#endif +#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) + #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) +#elif defined(_Check_return_) /* SAL */ + #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ +#else + #define JSON_HEDLEY_WARN_UNUSED_RESULT + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) +#endif + +#if defined(JSON_HEDLEY_SENTINEL) + #undef JSON_HEDLEY_SENTINEL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) +#else + #define JSON_HEDLEY_SENTINEL(position) +#endif + +#if defined(JSON_HEDLEY_NO_RETURN) + #undef JSON_HEDLEY_NO_RETURN +#endif +#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NO_RETURN __noreturn +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define JSON_HEDLEY_NO_RETURN _Noreturn +#elif defined(__cplusplus) && (__cplusplus >= 201103L) + #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) +#else + #define JSON_HEDLEY_NO_RETURN +#endif + +#if defined(JSON_HEDLEY_NO_ESCAPE) + #undef JSON_HEDLEY_NO_ESCAPE +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) + #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) +#else + #define JSON_HEDLEY_NO_ESCAPE +#endif + +#if defined(JSON_HEDLEY_UNREACHABLE) + #undef JSON_HEDLEY_UNREACHABLE +#endif +#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) + #undef JSON_HEDLEY_UNREACHABLE_RETURN +#endif +#if defined(JSON_HEDLEY_ASSUME) + #undef JSON_HEDLEY_ASSUME +#endif +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_ASSUME(expr) __assume(expr) +#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) + #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) +#elif \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #if defined(__cplusplus) + #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) + #else + #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) + #endif +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() +#elif defined(JSON_HEDLEY_ASSUME) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif +#if !defined(JSON_HEDLEY_ASSUME) + #if defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) + #else + #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) + #endif +#endif +#if defined(JSON_HEDLEY_UNREACHABLE) + #if \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) + #else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() + #endif +#else + #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) +#endif +#if !defined(JSON_HEDLEY_UNREACHABLE) + #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) +#endif + +JSON_HEDLEY_DIAGNOSTIC_PUSH +#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") + #pragma clang diagnostic ignored "-Wpedantic" +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) + #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#endif +#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) + #if defined(__clang__) + #pragma clang diagnostic ignored "-Wvariadic-macros" + #elif defined(JSON_HEDLEY_GCC_VERSION) + #pragma GCC diagnostic ignored "-Wvariadic-macros" + #endif +#endif +#if defined(JSON_HEDLEY_NON_NULL) + #undef JSON_HEDLEY_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) +#else + #define JSON_HEDLEY_NON_NULL(...) +#endif +JSON_HEDLEY_DIAGNOSTIC_POP + +#if defined(JSON_HEDLEY_PRINTF_FORMAT) + #undef JSON_HEDLEY_PRINTF_FORMAT +#endif +#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) +#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) +#elif \ + JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) +#else + #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) +#endif + +#if defined(JSON_HEDLEY_CONSTEXPR) + #undef JSON_HEDLEY_CONSTEXPR +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) + #endif +#endif +#if !defined(JSON_HEDLEY_CONSTEXPR) + #define JSON_HEDLEY_CONSTEXPR +#endif + +#if defined(JSON_HEDLEY_PREDICT) + #undef JSON_HEDLEY_PREDICT +#endif +#if defined(JSON_HEDLEY_LIKELY) + #undef JSON_HEDLEY_LIKELY +#endif +#if defined(JSON_HEDLEY_UNLIKELY) + #undef JSON_HEDLEY_UNLIKELY +#endif +#if defined(JSON_HEDLEY_UNPREDICTABLE) + #undef JSON_HEDLEY_UNPREDICTABLE +#endif +#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) + #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) +#endif +#if \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) +#elif \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ + (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ + })) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ + (__extension__ ({ \ + double hedley_probability_ = (probability); \ + ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ + })) +# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) +# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#else +# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) +# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) +# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) +# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) +#endif +#if !defined(JSON_HEDLEY_UNPREDICTABLE) + #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) +#endif + +#if defined(JSON_HEDLEY_MALLOC) + #undef JSON_HEDLEY_MALLOC +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_MALLOC __declspec(restrict) +#else + #define JSON_HEDLEY_MALLOC +#endif + +#if defined(JSON_HEDLEY_PURE) + #undef JSON_HEDLEY_PURE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PURE __attribute__((__pure__)) +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) +# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ + ) +# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") +#else +# define JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_CONST) + #undef JSON_HEDLEY_CONST +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_CONST __attribute__((__const__)) +#elif \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) + #define JSON_HEDLEY_CONST _Pragma("no_side_effect") +#else + #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE +#endif + +#if defined(JSON_HEDLEY_RESTRICT) + #undef JSON_HEDLEY_RESTRICT +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT restrict +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RESTRICT __restrict +#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) + #define JSON_HEDLEY_RESTRICT _Restrict +#else + #define JSON_HEDLEY_RESTRICT +#endif + +#if defined(JSON_HEDLEY_INLINE) + #undef JSON_HEDLEY_INLINE +#endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + (defined(__cplusplus) && (__cplusplus >= 199711L)) + #define JSON_HEDLEY_INLINE inline +#elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) + #define JSON_HEDLEY_INLINE __inline__ +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_INLINE __inline +#else + #define JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_ALWAYS_INLINE) + #undef JSON_HEDLEY_ALWAYS_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) +# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_ALWAYS_INLINE __forceinline +#elif defined(__cplusplus) && \ + ( \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ + ) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") +#else +# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE +#endif + +#if defined(JSON_HEDLEY_NEVER_INLINE) + #undef JSON_HEDLEY_NEVER_INLINE +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ + (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") +#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) + #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") +#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) + #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) + #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) +#else + #define JSON_HEDLEY_NEVER_INLINE +#endif + +#if defined(JSON_HEDLEY_PRIVATE) + #undef JSON_HEDLEY_PRIVATE +#endif +#if defined(JSON_HEDLEY_PUBLIC) + #undef JSON_HEDLEY_PUBLIC +#endif +#if defined(JSON_HEDLEY_IMPORT) + #undef JSON_HEDLEY_IMPORT +#endif +#if defined(_WIN32) || defined(__CYGWIN__) +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC __declspec(dllexport) +# define JSON_HEDLEY_IMPORT __declspec(dllimport) +#else +# if \ + JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + ( \ + defined(__TI_EABI__) && \ + ( \ + (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ + ) \ + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) +# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) +# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) +# else +# define JSON_HEDLEY_PRIVATE +# define JSON_HEDLEY_PUBLIC +# endif +# define JSON_HEDLEY_IMPORT extern +#endif + +#if defined(JSON_HEDLEY_NO_THROW) + #undef JSON_HEDLEY_NO_THROW +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) + #define JSON_HEDLEY_NO_THROW __declspec(nothrow) +#else + #define JSON_HEDLEY_NO_THROW +#endif + +#if defined(JSON_HEDLEY_FALL_THROUGH) + #undef JSON_HEDLEY_FALL_THROUGH +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) + #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) +#elif defined(__fallthrough) /* SAL */ + #define JSON_HEDLEY_FALL_THROUGH __fallthrough +#else + #define JSON_HEDLEY_FALL_THROUGH +#endif + +#if defined(JSON_HEDLEY_RETURNS_NON_NULL) + #undef JSON_HEDLEY_RETURNS_NON_NULL +#endif +#if \ + JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) +#elif defined(_Ret_notnull_) /* SAL */ + #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ +#else + #define JSON_HEDLEY_RETURNS_NON_NULL +#endif + +#if defined(JSON_HEDLEY_ARRAY_PARAM) + #undef JSON_HEDLEY_ARRAY_PARAM +#endif +#if \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(__STDC_NO_VLA__) && \ + !defined(__cplusplus) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_TINYC_VERSION) + #define JSON_HEDLEY_ARRAY_PARAM(name) (name) +#else + #define JSON_HEDLEY_ARRAY_PARAM(name) +#endif + +#if defined(JSON_HEDLEY_IS_CONSTANT) + #undef JSON_HEDLEY_IS_CONSTANT +#endif +#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) + #undef JSON_HEDLEY_REQUIRE_CONSTEXPR +#endif +/* JSON_HEDLEY_IS_CONSTEXPR_ is for + HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #undef JSON_HEDLEY_IS_CONSTEXPR_ +#endif +#if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ + (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) +#endif +#if !defined(__cplusplus) +# if \ + JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) +#endif +# elif \ + ( \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ + !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ + !defined(JSON_HEDLEY_PGI_VERSION) && \ + !defined(JSON_HEDLEY_IAR_VERSION)) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ + JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) +#if defined(__INTPTR_TYPE__) + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) +#else + #include + #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) +#endif +# elif \ + defined(JSON_HEDLEY_GCC_VERSION) || \ + defined(JSON_HEDLEY_INTEL_VERSION) || \ + defined(JSON_HEDLEY_TINYC_VERSION) || \ + defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ + defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ + defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ + defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ + defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ + defined(__clang__) +# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ + sizeof(void) != \ + sizeof(*( \ + 1 ? \ + ((void*) ((expr) * 0L) ) : \ +((struct { char v[sizeof(void) * 2]; } *) 1) \ + ) \ + ) \ + ) +# endif +#endif +#if defined(JSON_HEDLEY_IS_CONSTEXPR_) + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) +#else + #if !defined(JSON_HEDLEY_IS_CONSTANT) + #define JSON_HEDLEY_IS_CONSTANT(expr) (0) + #endif + #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) +#endif + +#if defined(JSON_HEDLEY_BEGIN_C_DECLS) + #undef JSON_HEDLEY_BEGIN_C_DECLS +#endif +#if defined(JSON_HEDLEY_END_C_DECLS) + #undef JSON_HEDLEY_END_C_DECLS +#endif +#if defined(JSON_HEDLEY_C_DECL) + #undef JSON_HEDLEY_C_DECL +#endif +#if defined(__cplusplus) + #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { + #define JSON_HEDLEY_END_C_DECLS } + #define JSON_HEDLEY_C_DECL extern "C" +#else + #define JSON_HEDLEY_BEGIN_C_DECLS + #define JSON_HEDLEY_END_C_DECLS + #define JSON_HEDLEY_C_DECL +#endif + +#if defined(JSON_HEDLEY_STATIC_ASSERT) + #undef JSON_HEDLEY_STATIC_ASSERT +#endif +#if \ + !defined(__cplusplus) && ( \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + defined(_Static_assert) \ + ) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) +#elif \ + (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) +#else +# define JSON_HEDLEY_STATIC_ASSERT(expr, message) +#endif + +#if defined(JSON_HEDLEY_NULL) + #undef JSON_HEDLEY_NULL +#endif +#if defined(__cplusplus) + #if __cplusplus >= 201103L + #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) + #elif defined(NULL) + #define JSON_HEDLEY_NULL NULL + #else + #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) + #endif +#elif defined(NULL) + #define JSON_HEDLEY_NULL NULL +#else + #define JSON_HEDLEY_NULL ((void*) 0) +#endif + +#if defined(JSON_HEDLEY_MESSAGE) + #undef JSON_HEDLEY_MESSAGE +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_MESSAGE(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(message msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) +#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) +#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) +# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_WARNING) + #undef JSON_HEDLEY_WARNING +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") +# define JSON_HEDLEY_WARNING(msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ + JSON_HEDLEY_PRAGMA(clang warning msg) \ + JSON_HEDLEY_DIAGNOSTIC_POP +#elif \ + JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) +#else +# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) +#endif + +#if defined(JSON_HEDLEY_REQUIRE) + #undef JSON_HEDLEY_REQUIRE +#endif +#if defined(JSON_HEDLEY_REQUIRE_MSG) + #undef JSON_HEDLEY_REQUIRE_MSG +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) +# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") +# define JSON_HEDLEY_REQUIRE(expr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), #expr, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ + __attribute__((diagnose_if(!(expr), msg, "error"))) \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) +# endif +#else +# define JSON_HEDLEY_REQUIRE(expr) +# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) +#endif + +#if defined(JSON_HEDLEY_FLAGS) + #undef JSON_HEDLEY_FLAGS +#endif +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) + #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS +#endif + +#if defined(JSON_HEDLEY_FLAGS_CAST) + #undef JSON_HEDLEY_FLAGS_CAST +#endif +#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) +# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("warning(disable:188)") \ + ((T) (expr)); \ + JSON_HEDLEY_DIAGNOSTIC_POP \ + })) +#else +# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) +#endif + +#if defined(JSON_HEDLEY_EMPTY_BASES) + #undef JSON_HEDLEY_EMPTY_BASES +#endif +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) +#else + #define JSON_HEDLEY_EMPTY_BASES +#endif + +/* Remaining macros are deprecated. */ + +#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) + #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK +#endif +#if defined(__clang__) + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) +#else + #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) +#endif + +#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) + #undef JSON_HEDLEY_CLANG_HAS_BUILTIN +#endif +#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) + +#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) + #undef JSON_HEDLEY_CLANG_HAS_FEATURE +#endif +#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) + +#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) + #undef JSON_HEDLEY_CLANG_HAS_EXTENSION +#endif +#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) + +#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) + #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE +#endif +#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) + +#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) + #undef JSON_HEDLEY_CLANG_HAS_WARNING +#endif +#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) + +#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ + + +// This file contains all internal macro definitions (except those affecting ABI) +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// #include + + +// exclude unsupported compilers +#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) + #if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif + #endif +#endif + +// C++ language standard detection +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 +#endif + +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + +#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) + #ifdef JSON_HAS_CPP_17 + #if defined(__cpp_lib_filesystem) + #define JSON_HAS_FILESYSTEM 1 + #elif defined(__cpp_lib_experimental_filesystem) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif !defined(__has_include) + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_FILESYSTEM 1 + #elif __has_include() + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 + #endif + + // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ + #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support + #if defined(__clang_major__) && __clang_major__ < 7 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support + #if defined(_MSC_VER) && _MSC_VER < 1914 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before iOS 13 + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + + // no filesystem support before macOS Catalina + #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 + #undef JSON_HAS_FILESYSTEM + #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #endif + #endif +#endif + +#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM + #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_FILESYSTEM + #define JSON_HAS_FILESYSTEM 0 +#endif + +#ifndef JSON_HAS_THREE_WAY_COMPARISON + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L + #define JSON_HAS_THREE_WAY_COMPARISON 1 + #else + #define JSON_HAS_THREE_WAY_COMPARISON 0 + #endif +#endif + +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifndef JSON_HAS_STATIC_RTTI + #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 + #define JSON_HAS_STATIC_RTTI 1 + #else + #define JSON_HAS_STATIC_RTTI 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + +#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) + #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else + #define JSON_NO_UNIQUE_ADDRESS +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdocumentation" + #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#endif + +// allow disabling exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) + #define JSON_INTERNAL_CATCH(exception) catch(exception) +#else + #include + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) + #define JSON_INTERNAL_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_CATCH_USER +#endif +#if defined(JSON_INTERNAL_CATCH_USER) + #undef JSON_INTERNAL_CATCH + #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER +#endif + +// allow overriding assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + +/*! +@brief macro to briefly define a mapping between an enum and JSON +@def NLOHMANN_JSON_SERIALIZE_ENUM +@since version 3.4.0 +*/ +#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ + template \ + inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [e](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.first == e; \ + }); \ + j = ((it != std::end(m)) ? it : std::begin(m))->second; \ + } \ + template \ + inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ + { \ + static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ + static const std::pair m[] = __VA_ARGS__; \ + auto it = std::find_if(std::begin(m), std::end(m), \ + [&j](const std::pair& ej_pair) -> bool \ + { \ + return ej_pair.second == j; \ + }); \ + e = ((it != std::end(m)) ? it : std::begin(m))->first; \ + } + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer, \ + class BinaryType, \ + class CustomBaseClass> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); +#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } + +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +// inspired from https://stackoverflow.com/a/26745591 +// allows to call any std function as if (e.g. with begin): +// using std::begin; begin(x); +// +// it allows using the detected idiom to retrieve the return type +// of such an expression +#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ + namespace detail { \ + using std::std_name; \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + } \ + \ + namespace detail2 { \ + struct std_name##_tag \ + { \ + }; \ + \ + template \ + std_name##_tag std_name(T&&...); \ + \ + template \ + using result_of_##std_name = decltype(std_name(std::declval()...)); \ + \ + template \ + struct would_call_std_##std_name \ + { \ + static constexpr auto const value = ::nlohmann::detail:: \ + is_detected_exact::value; \ + }; \ + } /* namespace detail2 */ \ + \ + template \ + struct would_call_std_##std_name : detail2::would_call_std_##std_name \ + { \ + } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + +#ifndef JSON_DISABLE_ENUM_SERIALIZATION + #define JSON_DISABLE_ENUM_SERIALIZATION 0 +#endif + +#ifndef JSON_USE_GLOBAL_UDLS + #define JSON_USE_GLOBAL_UDLS 1 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +template +inline void replace_substring(StringType& s, const StringType& f, + const StringType& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != StringType::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +template +inline StringType escape(StringType s) +{ + replace_substring(s, StringType{"~"}, StringType{"~0"}); + replace_substring(s, StringType{"/"}, StringType{"~1"}); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +template +static void unescape(StringType& s) +{ + replace_substring(s, StringType{"~1"}, StringType{"/"}); + replace_substring(s, StringType{"~0"}, StringType{"~"}); +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // size_t + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2018 The Abseil Authors +// SPDX-License-Identifier: MIT + + + +#include // array +#include // size_t +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. + +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence +{ + using value_type = T; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal + +// Compile-time sequences of integers + +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; + +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; + +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template +using index_sequence_for = make_index_sequence; + +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +// taken from ranges-v3 +template +struct static_const +{ + static JSON_INLINE_VARIABLE constexpr T value{}; +}; + +#ifndef JSON_HAS_CPP_17 + template + constexpr T static_const::value; +#endif + +template +inline constexpr std::array make_array(Args&& ... args) +{ + return std::array {{static_cast(std::forward(args))...}}; +} + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // numeric_limits +#include // false_type, is_constructible, is_integral, is_same, true_type +#include // declval +#include // tuple +#include // char_traits + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +#include // random_access_iterator_tag + +// #include + +// #include + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN +namespace detail +{ + +template +struct iterator_types {}; + +template +struct iterator_types < + It, + void_t> +{ + using difference_type = typename It::difference_type; + using value_type = typename It::value_type; + using pointer = typename It::pointer; + using reference = typename It::reference; + using iterator_category = typename It::iterator_category; +}; + +// This is required as some compilers implement std::iterator_traits in a way that +// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. +template +struct iterator_traits +{ +}; + +template +struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> + : iterator_types +{ +}; + +template +struct iterator_traits::value>> +{ + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = ptrdiff_t; + using pointer = T*; + using reference = T&; +}; + +} // namespace detail +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); + +NLOHMANN_JSON_NAMESPACE_END + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// #include + + +NLOHMANN_JSON_NAMESPACE_BEGIN + +NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); + +NLOHMANN_JSON_NAMESPACE_END + +// #include + +// #include + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ + #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + #include // int64_t, uint64_t + #include // map + #include // allocator + #include // string + #include // vector + + // #include + + + /*! + @brief namespace for Niels Lohmann + @see https://github.com/nlohmann + @since version 1.0.0 + */ + NLOHMANN_JSON_NAMESPACE_BEGIN + + /*! + @brief default JSONSerializer template argument + + This serializer ignores the template arguments and uses ADL + ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) + for serialization. + */ + template + struct adl_serializer; + + /// a class to store JSON values + /// @sa https://json.nlohmann.me/api/basic_json/ + template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector, // cppcheck-suppress syntaxError + class CustomBaseClass = void> + class basic_json; + + /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document + /// @sa https://json.nlohmann.me/api/json_pointer/ + template + class json_pointer; + + /*! + @brief default specialization + @sa https://json.nlohmann.me/api/json/ + */ + using json = basic_json<>; + + /// @brief a minimal map-like container that preserves insertion order + /// @sa https://json.nlohmann.me/api/ordered_map/ + template + struct ordered_map; + + /// @brief specialization that maintains the insertion order of object keys + /// @sa https://json.nlohmann.me/api/ordered_json/ + using ordered_json = basic_json; + + NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ + + +NLOHMANN_JSON_NAMESPACE_BEGIN +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ + +///////////// +// helpers // +///////////// + +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// used by exceptions create() member functions +// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t +// false_type otherwise +template +struct is_basic_json_context : + std::integral_constant < bool, + is_basic_json::type>::type>::value + || std::is_same::value > +{}; + +////////////////////// +// json_ref helpers // +////////////////////// + +template +class json_ref; + +template +struct is_json_ref : std::false_type {}; + +template +struct is_json_ref> : std::true_type {}; + +////////////////////////// +// aliases for detected // +////////////////////////// + +template +using mapped_type_t = typename T::mapped_type; + +template +using key_type_t = typename T::key_type; + +template +using value_type_t = typename T::value_type; + +template +using difference_type_t = typename T::difference_type; + +template +using pointer_t = typename T::pointer; + +template +using reference_t = typename T::reference; + +template +using iterator_category_t = typename T::iterator_category; + +template +using to_json_function = decltype(T::to_json(std::declval()...)); + +template +using from_json_function = decltype(T::from_json(std::declval()...)); + +template +using get_template_function = decltype(std::declval().template get()); + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +// trait checking if j.get is valid +// use this trait instead of std::is_constructible or std::is_convertible, +// both rely on, or make use of implicit conversions, and thus fail when T +// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) +template +struct is_getable +{ + static constexpr bool value = is_detected::value; +}; + +template +struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +template +using detect_key_compare = typename T::key_compare; + +template +struct has_key_compare : std::integral_constant::value> {}; + +// obtains the actual object key comparator +template +struct actual_object_comparator +{ + using object_t = typename BasicJsonType::object_t; + using object_comparator_t = typename BasicJsonType::default_object_comparator_t; + using type = typename std::conditional < has_key_compare::value, + typename object_t::key_compare, object_comparator_t>::type; +}; + +template +using actual_object_comparator_t = typename actual_object_comparator::type; + +///////////////// +// char_traits // +///////////////// + +// Primary template of char_traits calls std char_traits +template +struct char_traits : std::char_traits +{}; + +// Explicitly define char traits for unsigned char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = unsigned char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +// Explicitly define char traits for signed char since it is not standard +template<> +struct char_traits : std::char_traits +{ + using char_type = signed char; + using int_type = uint64_t; + + // Redefine to_int_type function + static int_type to_int_type(char_type c) noexcept + { + return static_cast(c); + } + + static char_type to_char_type(int_type i) noexcept + { + return static_cast(i); + } + + static constexpr int_type eof() noexcept + { + return static_cast(EOF); + } +}; + +/////////////////// +// is_ functions // +/////////////////// + +// https://en.cppreference.com/w/cpp/types/conjunction +template struct conjunction : std::true_type { }; +template struct conjunction : B { }; +template +struct conjunction +: std::conditional(B::value), conjunction, B>::type {}; + +// https://en.cppreference.com/w/cpp/types/negation +template struct negation : std::integral_constant < bool, !B::value > { }; + +// Reimplementation of is_constructible and is_default_constructible, due to them being broken for +// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). +// This causes compile errors in e.g. clang 3.5 or gcc 4.9. +template +struct is_default_constructible : std::is_default_constructible {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction, is_default_constructible> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_default_constructible> + : conjunction...> {}; + +template +struct is_constructible : std::is_constructible {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_constructible> : is_default_constructible> {}; + +template +struct is_iterator_traits : std::false_type {}; + +template +struct is_iterator_traits> +{ + private: + using traits = iterator_traits; + + public: + static constexpr auto value = + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value && + is_detected::value; +}; + +template +struct is_range +{ + private: + using t_ref = typename std::add_lvalue_reference::type; + + using iterator = detected_t; + using sentinel = detected_t; + + // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator + // and https://en.cppreference.com/w/cpp/iterator/sentinel_for + // but reimplementing these would be too much work, as a lot of other concepts are used underneath + static constexpr auto is_iterator_begin = + is_iterator_traits>::value; + + public: + static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; +}; + +template +using iterator_t = enable_if_t::value, result_of_begin())>>; + +template +using range_value_t = value_type_t>>; + +// The following implementation of is_complete_type is taken from +// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ +// and is written by Xiang Fan who agreed to using it in this library. + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl < + BasicJsonType, CompatibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + // macOS's is_constructible does not play well with nonesuch... + static constexpr bool value = + is_constructible::value && + is_constructible::value; +}; + +template +struct is_compatible_object_type + : is_compatible_object_type_impl {}; + +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t < is_detected::value&& + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + (is_default_constructible::value && + (std::is_move_assignable::value || + std::is_copy_assignable::value) && + (is_constructible::value && + std::is_same < + typename object_t::mapped_type, + typename ConstructibleObjectType::mapped_type >::value)) || + (has_from_json::value || + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + +template +struct is_compatible_string_type +{ + static constexpr auto value = + is_constructible::value; +}; + +template +struct is_constructible_string_type +{ + // launder type through decltype() to fix compilation failure on ICPC +#ifdef __INTEL_COMPILER + using laundered_type = decltype(std::declval()); +#else + using laundered_type = ConstructibleStringType; +#endif + + static constexpr auto value = + conjunction < + is_constructible, + is_detected_exact>::value; +}; + +template +struct is_compatible_array_type_impl : std::false_type {}; + +template +struct is_compatible_array_type_impl < + BasicJsonType, CompatibleArrayType, + enable_if_t < + is_detected::value&& + is_iterator_traits>>::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 + !std::is_same>::value >> +{ + static constexpr bool value = + is_constructible>::value; +}; + +template +struct is_compatible_array_type + : is_compatible_array_type_impl {}; + +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t < !std::is_same::value&& + !is_compatible_string_type::value&& + is_default_constructible::value&& +(std::is_move_assignable::value || + std::is_copy_assignable::value)&& +is_detected::value&& +is_iterator_traits>>::value&& +is_detected::value&& +// special case for types like std::filesystem::path whose iterator's value_type are themselves +// c.f. https://github.com/nlohmann/json/pull/3073 +!std::is_same>::value&& + is_complete_type < + detected_t>::value >> +{ + using value_type = range_value_t; + + static constexpr bool value = + std::is_same::value || + has_from_json::value || + has_non_default_from_json < + BasicJsonType, + value_type >::value; +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl < + RealIntegerType, CompatibleNumberIntegerType, + enable_if_t < std::is_integral::value&& + std::is_integral::value&& + !std::is_same::value >> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + is_constructible::value && + CompatibleLimits::is_integer && + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type + : is_compatible_integer_type_impl {}; + +template +struct is_compatible_type_impl: std::false_type {}; + +template +struct is_compatible_type_impl < + BasicJsonType, CompatibleType, + enable_if_t::value >> +{ + static constexpr bool value = + has_to_json::value; +}; + +template +struct is_compatible_type + : is_compatible_type_impl {}; + +template +struct is_constructible_tuple : std::false_type {}; + +template +struct is_constructible_tuple> : conjunction...> {}; + +template +struct is_json_iterator_of : std::false_type {}; + +template +struct is_json_iterator_of : std::true_type {}; + +template +struct is_json_iterator_of : std::true_type +{}; + +// checks if a given type T is a template specialization of Primary +template