From 73fe6e48f61d79b5e95a726ce32d463a22373af7 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 23 Dec 2024 03:53:18 +0100 Subject: [PATCH] Refactor bot_char creation + capsulating char- & skill helper fns --- db/re/skill_tree.yml | 6 +- src/bot/Brokk/brokk.cpp | 518 ++++--- src/bot/Brokk/brokk.hpp | 9 +- src/bot/Brokk/char_creation_helpers.cpp | 367 ++++- src/bot/Brokk/char_creation_helpers.hpp | 10 +- src/bot/Brokk/charakters.cpp | 12 +- src/bot/Brokk/helper_skills.cpp | 580 +++++++ src/bot/Brokk/helper_skills.hpp | 1865 +---------------------- 8 files changed, 1279 insertions(+), 2088 deletions(-) diff --git a/db/re/skill_tree.yml b/db/re/skill_tree.yml index d9213f4..c0dd129 100644 --- a/db/re/skill_tree.yml +++ b/db/re/skill_tree.yml @@ -1533,7 +1533,7 @@ Body: Novice: true Swordman: true Crusader: true - - Job: Supernovice + - Job: Super_novice Inherit: Novice: true Tree: @@ -5574,7 +5574,7 @@ Body: - Job: Super_Novice_E Inherit: Novice: true - Supernovice: true + Super_novice: true Tree: - Name: PR_IMPOSITIO MaxLevel: 5 @@ -7956,7 +7956,7 @@ Body: - Job: Hyper_Novice Inherit: Novice: true - Supernovice: true + Super_novice: true Super_Novice_E: true Tree: - Name: HN_SELFSTUDY_TATICS diff --git a/src/bot/Brokk/brokk.cpp b/src/bot/Brokk/brokk.cpp index cabe602..5b65ee3 100644 --- a/src/bot/Brokk/brokk.cpp +++ b/src/bot/Brokk/brokk.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "pc.hpp" @@ -37,6 +38,7 @@ #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" @@ -56,7 +58,7 @@ SkillDB* skills = nullptr; // YAML DB Objects of all touched infrastucture BotStatPointDatabase bot_statpoint_db; BotJobDatabase bot_job_db; -//SkillTreeDatabase skill_tree_db; +//BotSkillTreeDatabase bot_skill_tree_db; // Advanced subnet check [LuzZza] struct s_subnet { @@ -77,7 +79,10 @@ struct online_login_data* login_get_online_user(uint32 account_id) { */ TIMER_FUNC(periodic_cleanup_timer) { - bot_account_cleanup(); // Call the cleanup function + // Warning!!!: This will auto delete all bots after a certain time, even in the middle of sessions. + // Bots will not be logged out upon deletion. + + //bot_account_cleanup(); // Call the cleanup function only if configured return 0; // Return 0 to keep the timer going } @@ -101,7 +106,7 @@ void setup_periodic_cleanup_timer() { * 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") { +int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, 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(); @@ -141,6 +146,7 @@ int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, con 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)); + const char* last_ip = "127.0.0.1"; //Allways localhost because bots will never be created elsewhere safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip)); safestrncpy(acc.birthdate, "", sizeof(acc.birthdate)); safestrncpy(acc.pincode, "", sizeof(acc.pincode)); @@ -241,7 +247,9 @@ int brokk_mmo_auth_delete_num(uint32 account_id) { struct mmo_account acc; struct mmo_bot_account bot_acc; struct mmo_charakter charakter; + struct bot_skill skill; std::vector char_ids_to_delete; + std::vector skills_to_delete; // Check if the account exists if (!login_accounts->load_num(login_accounts, &acc, account_id)) { @@ -257,17 +265,18 @@ int brokk_mmo_auth_delete_num(uint32 account_id) { return 1; // Bot account does not exist } - // Delete login account and corresponding bot account + // Step 1: Account Data (login table) 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 } + // Step 2: Bot-Account Data (bot_accounts table) 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) + // Step 3: Character Data (char table) // First pass: Collect char_ids to delete based on config CharakterDBIterator* iter_chars = charakters->iterator(charakters); // Create the iterator @@ -281,13 +290,38 @@ int brokk_mmo_auth_delete_num(uint32 account_id) { } iter_chars->destroy(iter_chars); // Destroy the iterator after use + ShowDebug("DelMarco\n"); // Second pass: Delete the collected charakters for (const auto& char_id : char_ids_to_delete) { + ShowDebug("DelPolo1\n"); 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 } + ShowDebug("DelPolo2\n"); + // Step 3B: Attempt to delete skills of the char (if any) + // First pass: Collect skill_ids to delete based on config + SkillDBIterator* iter_skills = skills->iterator(skills, char_id); // Create the iterator + while (iter_skills->next(iter_skills, char_id, &skill)) { + std::string id_str = convert_skill_id_to_str(skill.skill_id); + //ShowDebug("Skill %s found. Schedule for deletion...\n", id_str.c_str()); + skills_to_delete.push_back(skill); + } + iter_skills->destroy(iter_skills); // Destroy the iterator after use + ShowDebug("DelPolo3\n"); + // Second pass: Delete the collected skills + for (const auto& skill_entry : skills_to_delete) { + //ShowInfo("Deleting skill with ID %d.\n", char_id); + if (!skills->remove(skills, char_id, skill_entry.skill_id)) { + ShowError("Failed to delete skill (skill_id: %s)\n", skill_entry.skill_id); + continue; // Deletion failed + } + else { + ShowNotice("Deleting of skill %s from char_id %d was successful\n", convert_skill_id_to_str(skill_entry.skill_id), char_id); + } + } + ShowDebug("DelPolo4\n"); } ShowNotice("Bot account deletion completed (account: %s, id: %d)\n\n", acc.userid, acc.account_id); @@ -449,6 +483,8 @@ void brokk_set_defaults() { void bot_account_cleanup() { + ShowStatus("Start bot deletion according to settings...\n"); + struct mmo_bot_account bot_acc; BotAccountDBIterator* iter_bots = bot_accounts->iterator(bot_accounts); std::vector account_ids_to_delete; @@ -467,11 +503,12 @@ void bot_account_cleanup() { // 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); + //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"); + ShowStatus("Bot account destruction completed successfully.\n\n"); } /* * initializes dbs for all used non-yaml databases @@ -547,175 +584,114 @@ void set_last_login(char last_login[24]) { } /** - * 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 + * Create a new character and save it in the database. + * @param charakter: Pre-configured mmo_charakter object with all necessary fields populated. * @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) + * 0: error + * >0: new char id */ -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 +bool brokk_create_character(mmo_charakter& charakter, int& char_id, const bool auto_generated) { + static int num_regs = 0; // Character 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 + mmo_account acc; + if (!login_accounts->load_num(login_accounts, &acc, charakter.account_id)) { + ShowNotice("Attempt to create character for non-existent account (account_id: %u)\n", charakter.account_id); + return false; // Account does not exist } - // char registration flood protection - if (new_reg_tick == 0) - new_reg_tick = gettick(); - if (auto_generated == false) { + // Registration flood protection + if (!auto_generated) { + if (new_reg_tick == 0) + new_reg_tick = gettick(); 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; + ShowNotice("Character registration denied (registration limit exceeded)\n"); + return false; } } - // 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); + // Validate character's sex input (must be 'M' or 'F') + if (charakter.sex != 'M' && charakter.sex != 'F') { + ShowNotice("Invalid character sex (char_name: %s, sex: %c)\n", charakter.name, charakter.sex); + return false; // Unregistered ID + } + + // Check if the character already exists + mmo_charakter existing_char; + if (charakters->load_str(charakters, &existing_char, charakter.name)) { + ShowNotice("Attempt to create an already existing character (char_name: %s, sex: %c)\n", charakter.name, charakter.sex); + char_id = 1; + return false; // Character exists + } + + std::string char_dump = dump_mmo_charakter(charakter); + //ShowDebug(char_dump.c_str()); + // Create the character in the database + if (!charakters->create(charakters, &charakter)) { + ShowError("Failed to create character (char_name: %s)\n", charakter.name); + return false; // Character creation failed + } + + // Log success + ShowNotice("Character creation successful (name: %s, char_id: %d, sex: %c)\n", charakter.name, charakter.char_id, charakter.sex); - // Registration rate limit logic + // Update registration limit 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; + Sleep(10); + if (!charakters->load_str(charakters, &existing_char, charakter.name)) { + ShowError("Attempt to load newly created character failed (char_name: %s, sex: %c)\n", charakter.name, charakter.sex); + return false; // Character does not exists + } + + char_id = existing_char.char_id; + return true; // Success -> return new char_id +} + +/** + * Add a skill to a charakters bundle in the database. + * @param skill: Pre-configured bot_skill object with all necessary fields populated. + * @return : + * -1: success + * 1: unregistered id (invalid char_id) + * 2: Error while writing into skill table + */ +int brokk_teach_skills(bot_skill_bundle& skill_bundle, int& char_id) { + + int skill_count = 0; + + // Check if the character already exists + mmo_charakter existing_char; + if (!charakters->load_num(charakters, &existing_char, char_id)) { + ShowError("Attempt to add an skill to non-existing character (char_id: %d)\n", char_id); + return 1; // Character does not exist + } + + std::string char_name = existing_char.name; + + for (auto& skill : skill_bundle.skills) { + + // Create entry in the skill database + if (!skills->create(skills, &skill)) { + ShowError("Failed to create skill entry (char_id: %d, skill_id: %d)\n", skill.char_id, skill.skill_id); + return 2; // skill entry creation failed + } + + // Log success single skill + ShowNotice("Skill %s added to character char_id: %d)\n", convert_skill_id_to_str(skill.skill_id), skill.char_id); + skill_count++; + } + + // Log success + ShowStatus("Successfully applied %d Skills to character (name: %s, char_id: %d)\n", skill_count, char_name.c_str(), char_id); + + return -1; // Success } void BrokkServer::finalize() { @@ -730,7 +706,7 @@ void BrokkServer::finalize() { // brokklog_final(); // Attempt to delete the bot account on shutdown - bot_account_cleanup(); + //bot_account_cleanup(); if (db) { // destroy account engine db->destroy(db); @@ -768,7 +744,10 @@ void BrokkServer::finalize() { void BrokkServer::handle_shutdown() { ShowStatus("Shutting down...\n"); - + + // Attempt to delete the bot account on shutdown + bot_account_cleanup(); + flush_fifos(); } @@ -785,6 +764,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) { // Initialize Yaml-DBs bot_read_yaml_dbs(); + init_bot_skill_tree_db(); // read brokk-server configuration brokk_set_defaults(); @@ -796,8 +776,8 @@ bool BrokkServer::initialize(int argc, char* argv[]) { return false; } - /**/ - //debug skills von gm acc aufzählen + //debug: display skills of gm acc + /* SkillDBIterator* iter_skills = skills->iterator(skills, 150000); struct bot_skill skill; while (iter_skills->next(iter_skills, 150000, &skill)) { @@ -806,7 +786,21 @@ bool BrokkServer::initialize(int argc, char* argv[]) { } // Destroy the iterator after use iter_skills->destroy(iter_skills); - + */ + + /* + //Demo-Skill tree + //TODO Eintragen in Job-Profil -> welche Skills gelernt werden sollen und Mechanik bauen für plausibles auto-skill erlernen + std::shared_ptr tree = bot_skill_tree_db.find(JOB_MAGE); + for (const auto& skill : tree->skills){ + ShowDebug("Skill Found in tree: %s \n",convert_skill_id_to_str(skill.first)); + if (skill.first == MG_SAFETYWALL) { + ShowDebug("skill max lvl : %d \n", skill.second->max_lv); + for (const auto needed_skill : skill.second->need) { + ShowDebug("requires skill %s on level %d\n", convert_skill_id_to_str(needed_skill.first), needed_skill.second); + } + } + }*/ // initialize logging //if (brokk_config.log_brokk) @@ -847,7 +841,6 @@ bool BrokkServer::initialize(int argc, char* argv[]) { 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()); @@ -860,7 +853,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) { //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)) { + if (!brokk_mmo_auth_new(userid, pass.c_str(), sex, true)) { ShowError("Failed to create bot account (userid: %s)\n", userid); continue; // Skip to the next bot account } @@ -874,110 +867,141 @@ bool BrokkServer::initialize(int argc, char* argv[]) { } 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"]; + + if (bot.contains("characters")) { + const auto& json_characters = bot["characters"]; + //ShowDebug("StartChar Parsing"); + for (const auto& json_character : json_characters) { //Iterate every declared character and try to create it + //ShowDebug("char_json:\n%s", character.dump().c_str()); + + mmo_charakter new_char{}; + + //Step1 Meta-Data about character (allways have to be present/not bound to profile) + const std::string char_name = json_character["name"]; + safestrncpy(new_char.name, char_name.c_str(), NAME_LENGTH); + + std::string sex_str = json_character["sex"]; + new_char.sex = sex_str[0]; + new_char.char_num = json_character["slot"]; + new_char.zeny = json_character["zeny"]; + new_char.inventory_slots = 100; + + // Decode Job Value + std::string job_id = json_character["start_job"]; + new_char.class_ = convert_jobid_to_number(job_id.c_str()); + if (new_char.class_ == -1) { + job_id = "JOB_NOVICE"; + new_char.class_ = JOB_NOVICE; + } + // Step2: build data of character + new_char.account_id = account_id; + new_char.char_id = -1; + prepare_character_build_data(new_char, json_character, bot_job_db); - const int base_level = character["base_level"]; - const int job_level = character["job_level"]; + // Step3: appearance of character + prepare_character_appearance_data(new_char, json_character); - const int hair_style = character["hair_style"]; - const int hair_color = character["hair_color"]; - //TODO: Clothing/Style Randomizer hier einbauen + // Step4: location data of character + prepare_character_location_data(new_char, json_character); - //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; + // All char table related data is collected and can be saved to allow further steps (skills/items/equip) + int new_char_id = 0; + try { + if (!brokk_create_character(new_char, new_char_id, true)) { + + if (new_char_id < 1) { + ShowError("Failed to create character: %s\n", new_char.name); + continue; + } + else { + ShowNotice("Atempt to create existing character : % s (id: %d)\n", new_char.name, new_char_id); + continue; + } + }// otherwise character is correctly and newly created + } + catch (const std::exception& e) { + ShowError("Character creation for '%s 'failed: %s\n",new_char.name, e.what()); + continue; } - std::shared_ptr job = bot_job_db.find(start_job); + //TODO Ab hier debuggen warum bei EvaExample bei neuanlage crashed + + // Step5: Skills + // Prepare skills inside a skill bundle + bot_skill_bundle skill_bundle{}; - 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 - } + prepare_skill_bundle(skill_bundle, new_char_id, json_character); - // 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); + // All skill table related data is collected and can be saved + try { + if (!brokk_teach_skills(skill_bundle, new_char_id)) { + ShowError("Failed to create skills entry for: %s\n", new_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; - } + catch (const std::exception& e) { + ShowError("skill creation for '%s 'failed: %s\n", new_char.name, e.what()); + continue; + } - // Parse the JSON file - json json_stat_profiles; - try { - stat_profile_file >> json_stat_profiles; + ShowNotice("Skills successfully teached to character (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex); - } - 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(); + //Step4: Items + //TODO Testweise folgende ItemIDs einbauen für Debug: +// 1950, 2627 , 4040 + //ShowNotice("Items successfully handed out to character (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex); + + //Step5: Equipment + + //ShowNotice("Gear/Weapons successfully equiped (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex); - // 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; + ShowStatus("Bot-Character creation and administration completed (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex); + + /***** + * Validating/Applying skill build + ***** + // DEBUG: check skill plausibility + const auto& skill_bundle = character["skills"]; + ShowDebug("Skill bundle: %s\n", skill_bundle.dump(4).c_str()); // Ensure `skill_bundle` is structured as expected + + for (const auto& skill : skill_bundle.items()) { + ShowDebug("Skill: %s", skill.key().c_str()); + std::unordered_map required_skill; + //skill >> required_skill; + std::string skill_str = skill.key(); + int skill_id = convert_skill_str_to_id(skill_str.c_str()); + if (skill_id < 0) { // Assuming negative IDs are invalid + ShowError("Invalid skill ID for skill string: %s\n", skill_str.c_str()); + continue; // Skip this iteration } - 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"]; - } + std::unordered_map parsed_skill_bundle; + for (const auto& skill : skill_bundle.items()) { + uint16 skill_id = convert_skill_str_to_id(skill.key().c_str()); + uint16 level = skill.value(); // Assuming the value represents the skill level + parsed_skill_bundle[skill_id] = level; + } - // DEBUG: Show character details - //ShowStatus("Creating character: name=%s, slot=%d, start_job=%d\n", char_name.c_str(), slot, start_job); + bool skill_plausible = check_skill_requirements(start_job, skill_id, parsed_skill_bundle); + if (skill_plausible) { + ShowDebug("all requirements met for skill %s \n", skill.key()); + } + else { + ShowDebug("some requirements not met for skill %s , Cancel creation process?\n", skill.key()); + continue; + } + }*/ // Create the character for this account + // TODO rebuild to: + // if (!brokk_create_character(new_char, skill_build_data, item_data, equip_data true)) + /* 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 - } + }*/ } } @@ -988,7 +1012,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) { } // Other initialization logic - setup_periodic_cleanup_timer(); // Set up the cleanup timer + //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"); diff --git a/src/bot/Brokk/brokk.hpp b/src/bot/Brokk/brokk.hpp index bee7fd7..456f61f 100644 --- a/src/bot/Brokk/brokk.hpp +++ b/src/bot/Brokk/brokk.hpp @@ -11,6 +11,9 @@ #include // NAME_LENGTH,SEX_* #include #include +#include "charakters.hpp" +#include "char_creation_helpers.hpp" +#include "helper_skills.hpp" using rathena::server_core::Core; using rathena::server_core::e_core_type; @@ -57,7 +60,7 @@ 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_new(const char* userid, const char* pass, const char sex, 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); @@ -65,6 +68,10 @@ void brokk_set_defaults(); void setup_periodic_cleanup_timer(); void bot_account_cleanup(); bool brokk_initialize_dbs(); +bool brokk_create_character(mmo_charakter& charakter, int& char_id, const bool auto_generated); +int brokk_teach_skills(bot_skill_bundle& skill_bundle, int& char_id); +//int brokk_add_item(container_struct& container); +//int brokk_apply_equip(container_struct& container); #endif /* BROKK_HPP */ diff --git a/src/bot/Brokk/char_creation_helpers.cpp b/src/bot/Brokk/char_creation_helpers.cpp index 7e24202..9d24aed 100644 --- a/src/bot/Brokk/char_creation_helpers.cpp +++ b/src/bot/Brokk/char_creation_helpers.cpp @@ -6,6 +6,7 @@ #include "char_creation_helpers.hpp" #include +#include #include #include @@ -15,6 +16,8 @@ #include "charakters.hpp" +#include +#include using namespace rathena; @@ -652,6 +655,317 @@ static unsigned int pc_calc_basesp(uint16 level, uint16 job_id) { return (unsigned int)base_sp; } +void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& character, BotJobDatabase& bot_job_db) { + + // Validate and apply Build + std::string build_profile = character["char_build_profile"]; + + if (build_profile == "CUSTOM") { + if (!character.contains("build")) { + throw std::runtime_error("Build data is missing for CUSTOM build profile."); + } + + const auto& build_data = character["build"]; + new_char.base_level = build_data["base_level"]; + new_char.job_level = build_data["job_level"]; + + std::string job_str = character["start_job"]; + int job_id = convert_jobid_to_number(job_str.c_str()); + + //Base/Job EXP values (need to be set to technically calculate xp correctly) + uint64 base_exp = 0; + uint64 job_exp = 0; + + if (new_char.base_level > 1) { + base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level - 1); + } + + if (new_char.job_level > 1) { + job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level - 1); + } + + new_char.base_exp = base_exp; + new_char.job_exp = job_exp; + + new_char.inventory_slots = 100; //Default + + //TODO auslagern in calc fn + new_char.max_hp = 40; + new_char.hp = 40; + new_char.max_sp = 10; + new_char.sp = 10; + new_char.max_ap = 0; + new_char.ap = 0; + + new_char.str = build_data["str"]; + new_char.agi = build_data["agi"]; + new_char.vit = build_data["vit"]; + new_char.int_ = build_data["int"]; + new_char.dex = build_data["dex"]; + new_char.luk = build_data["luk"]; + + new_char.pow = build_data["pow"]; + new_char.sta = build_data["sta"]; + new_char.wis = build_data["wis"]; + new_char.spl = build_data["spl"]; + new_char.con = build_data["con"]; + new_char.crt = build_data["crt"]; + + } + else { + // Load Build Profile + std::string job_str = character["start_job"]; + int job_id = convert_jobid_to_number(job_str.c_str()); + std::string job_profile_file_path = "conf/bot_profiles/build_profiles/" + job_str + "_build_profile.json"; + nlohmann::json json_build_profiles; + + // Try to Open Build Profile + std::ifstream build_profile_file(job_profile_file_path); + if (!build_profile_file.is_open()) { + ShowWarning("Failed to open build_profile_file %s. Trying to load DEFAULT instead.", job_profile_file_path); + job_profile_file_path = "conf/bot_profiles/build_profiles/DEFAULT_build_profile.json"; + build_profile_file.open(job_profile_file_path); + if (!build_profile_file.is_open()) { + throw std::runtime_error("Failed to open both job and default profiles."); + } + } + + // Parse Profile Data + try { + build_profile_file >> json_build_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what())); + } + + if (!json_build_profiles.contains(build_profile)) { + // If build profile is not found, default to INIT + build_profile = "INIT"; + } + + const auto& profile = json_build_profiles[build_profile]; + + new_char.base_level = profile["base_level"]; + new_char.job_level = profile["job_level"]; + + //Check if chosen max level exceeds base level or job level maximum range + if (new_char.base_level > bot_job_db.get_maxBaseLv(job_id) or new_char.job_level > bot_job_db.get_maxJobLv(job_id)) + { + throw std::runtime_error("Error parsing build profile JSON: base_level or job_level exceeding allowed max_level"); + } + + //Base/Job EXP values (need to be set to technically calculate xp correctly) + uint32 base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level); + uint32 job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level); + + new_char.base_exp = base_exp; + new_char.job_exp = job_exp; + + //Set stats + new_char.str = profile["str"]; + new_char.agi = profile["agi"]; + new_char.vit = profile["vit"]; + new_char.int_ = profile["int"]; + new_char.dex = profile["dex"]; + new_char.luk = profile["luk"]; + + new_char.pow = profile["pow"]; + new_char.sta = profile["sta"]; + new_char.wis = profile["wis"]; + new_char.spl = profile["spl"]; + new_char.con = profile["con"]; + new_char.crt = profile["crt"]; + } + //ShowDebug("Character: \n%s",dump_mmo_charakter(new_char)); +} + +void prepare_character_appearance_data(mmo_charakter& new_char, const nlohmann::json& character){ + // Validate and apply appearance data + + std::string appearance_profile = character["char_appearance_profile"]; + + if (appearance_profile == "CUSTOM") { + if (!character.contains("appearance")) { + throw std::runtime_error("appearance data is missing for CUSTOM appearance profile."); + } + + const auto& appearance_data = character["appearance"]; + + //Set hair style + uint16 hair_style = appearance_data["hair_style"]; //Default max value = 23 + if (hair_style < 0) { + //Negative style value => random hair style + int random_hair_style = 1 + (rand() % 23); + new_char.hair = random_hair_style; + } + else { + new_char.hair = hair_style; + } + + // Set hair color + uint16 hair_color = appearance_data["hair_color"]; //Default max value = 8 + if (hair_color < 0) { + //Negative color value => random hair color + int random_hair_color = 1 + (rand() % 8); + new_char.hair_color = random_hair_color; + } + else { + new_char.hair_color = hair_color; + } + + // Set clothes color + uint16 clothes_color = appearance_data["clothes_color"]; //Default max value = ? + if (clothes_color < 0) { + //Negative color value => random clothes color + int random_clothes_color = 0; + new_char.clothes_color = random_clothes_color; + } + else { + new_char.clothes_color = clothes_color; + } + } + else { + + // Load appearance Profile + std::string appearance_profile_file_path = "conf/bot_profiles/appearance_profiles.json"; + nlohmann::json json_appearance_profiles; + + // Try to Open appearance Profile + std::ifstream appearance_profile_file(appearance_profile_file_path); + if (!appearance_profile_file.is_open()) { + std::string err_msg = ("Failed to open appearance_profile_file %s.", appearance_profile_file_path); + throw std::runtime_error(err_msg.c_str()); + } + + // Parse Profile Data + try { + appearance_profile_file >> json_appearance_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing appearance profile JSON: " + std::string(e.what())); + } + + if (!json_appearance_profiles.contains(appearance_profile)) { + // If build profile is not found, default to INIT + appearance_profile = "INIT"; + } + + const auto& profile = json_appearance_profiles[appearance_profile]; + + //Set hair style + uint16 hair_style = profile["hair_style"]; //Default max value = 23 + if (hair_style < 0) { + //Negative style value => random hair style + int random_hair_style = 1 + (rand() % 22); + new_char.hair = random_hair_style; + } + else { + new_char.hair = hair_style; + } + + // Set hair color + uint16 hair_color = profile["hair_color"]; //Default max value = 8 + if (hair_color < 0) { + //Negative color value => random hair color + //int random_hair_color = 1 + (rand() % 7); + int random_hair_color = 1; + new_char.hair_color = random_hair_color; + } + else { + new_char.hair_color = hair_color; + } + + // Set clothes color + uint16 clothes_color = profile["clothes_color"]; //Default max value = ? + if (clothes_color < 0) { + //Negative color value => random clothes color + int random_clothes_color = 0; + new_char.clothes_color = random_clothes_color; + } + else { + new_char.clothes_color = clothes_color; + } + } +} + +void prepare_character_location_data(mmo_charakter& new_char, const nlohmann::json& character) { + // Validate and apply location data + std::string location_profile = character["char_location_profile"]; + + if (location_profile == "CUSTOM") { + + if (!character.contains("location")) { + throw std::runtime_error("location data is missing for CUSTOM location profile."); + } + + const auto& location_data = character["location"]; + + //Set last map + std::string last_map = location_data["last_map"]; + safestrncpy(new_char.last_map, last_map.c_str(), sizeof(new_char.last_map)); + + new_char.last_x = location_data["last_x"]; + new_char.last_y = location_data["last_y"]; + + //Set saved map + std::string save_map = location_data["save_map"]; + safestrncpy(new_char.save_map, save_map.c_str(), sizeof(new_char.save_map)); + + new_char.save_x = location_data["save_x"]; + new_char.save_y = location_data["save_y"]; + + //Set Instance nr + new_char.last_instanceid = location_data["last_instanceid"]; + + } + else { + + // Load location Profile + std::string location_profile_file_path = "conf/bot_profiles/location_profiles.json"; + nlohmann::json json_location_profiles; + + // Try to Open location Profile + std::ifstream location_profile_file(location_profile_file_path); + if (!location_profile_file.is_open()) { + std::string err_msg = ("Failed to open location %s.", location_profile_file_path); + throw std::runtime_error(err_msg.c_str()); + } + + // Parse Profile Data + try { + location_profile_file >> json_location_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing location profile JSON: " + std::string(e.what())); + } + + if (!json_location_profiles.contains(location_profile)) { + // If build profile is not found, default to INIT + location_profile = "INIT"; + } + + const auto& profile = json_location_profiles[location_profile]; + + //Set last map + std::string last_map = profile["last_map"]; + safestrncpy(new_char.last_map, last_map.c_str(), sizeof(new_char.last_map)); + + new_char.last_x = profile["last_x"]; + new_char.last_y = profile["last_y"]; + + //Set saved map + std::string save_map = profile["save_map"]; + safestrncpy(new_char.save_map, save_map.c_str(), sizeof(new_char.save_map)); + + new_char.save_x = profile["save_x"]; + new_char.save_y = profile["save_y"]; + + //Set Instance nr + new_char.last_instanceid = profile["last_instanceid"]; + + } +} + /*========================================== * Convert's from the client's lame Job ID system * to the map server's 'makes sense' system. [Skotlex] @@ -679,7 +993,7 @@ uint64 pc_jobid2mapid(unsigned short b_class) 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_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; @@ -1420,7 +1734,56 @@ void bot_read_yaml_dbs(void) { 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_skill_tree_db.reload(); bot_statpoint_db.load(); } + +std::string dump_mmo_charakter(const mmo_charakter& charakter) { + std::string dump; + + dump += "Character Dump:\n"; + dump += "-----------------\n"; + dump += "char_id: " + std::to_string(charakter.char_id) + "\n"; + dump += "account_id: " + std::to_string(charakter.account_id) + "\n"; + dump += "char_num: " + std::to_string(charakter.char_num) + "\n"; + dump += "name: " + std::string(charakter.name) + "\n"; + + dump += "class: " + std::to_string(charakter.class_) + "\n"; + dump += "base_level: " + std::to_string(charakter.base_level) + "\n"; + dump += "job_level: " + std::to_string(charakter.job_level) + "\n"; + + dump += "base_exp: " + std::to_string(charakter.base_exp) + "\n"; + dump += "job_exp: " + std::to_string(charakter.job_exp) + "\n"; + dump += "zeny: " + std::to_string(charakter.zeny) + "\n"; + + dump += "Stats:\n"; + dump += " str: " + std::to_string(charakter.str) + "\n"; + dump += " agi: " + std::to_string(charakter.agi) + "\n"; + dump += " vit: " + std::to_string(charakter.vit) + "\n"; + dump += " int: " + std::to_string(charakter.int_) + "\n"; + dump += " dex: " + std::to_string(charakter.dex) + "\n"; + dump += " luk: " + std::to_string(charakter.luk) + "\n"; + + dump += "Health/Mana:\n"; + dump += " max_hp: " + std::to_string(charakter.max_hp) + "\n"; + dump += " hp: " + std::to_string(charakter.hp) + "\n"; + dump += " max_sp: " + std::to_string(charakter.max_sp) + "\n"; + dump += " sp: " + std::to_string(charakter.sp) + "\n"; + + dump += "Additional Info:\n"; + dump += " curr_map: " + std::string(charakter.last_map) + "\n"; + dump += " curr_x: " + std::to_string(charakter.last_x) + "\n"; + dump += " curr_y: " + std::to_string(charakter.last_y) + "\n"; + dump += " save_map: " + std::string(charakter.save_map) + "\n"; + dump += " save_x: " + std::to_string(charakter.save_x) + "\n"; + dump += " save_y: " + std::to_string(charakter.save_y) + "\n"; + + dump += "Sex: " + std::string(1, charakter.sex) + "\n"; + dump += "Last Login: " + std::string(charakter.last_login) + "\n"; + + dump += "Hair Style: " + std::to_string(charakter.hair) + "\n"; + dump += "Hair Color: " + std::to_string(charakter.hair_color) + "\n"; + + return dump; +} diff --git a/src/bot/Brokk/char_creation_helpers.hpp b/src/bot/Brokk/char_creation_helpers.hpp index 31f26d2..4870bdf 100644 --- a/src/bot/Brokk/char_creation_helpers.hpp +++ b/src/bot/Brokk/char_creation_helpers.hpp @@ -6,11 +6,14 @@ #ifndef CHAR_CREATION_HELPERS_HPP #define CHAR_CREATION_HELPERS_HPP +#include "json.hpp" +#include // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus + #include #include #include #include -#include // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus + #include "charakters.hpp" /* * Constants for yaml dbs @@ -366,4 +369,9 @@ extern BotJobDatabase bot_job_db; void bot_read_yaml_dbs(void); +void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& character, BotJobDatabase& bot_job_db); +void prepare_character_appearance_data(mmo_charakter& new_char, const nlohmann::json& character); +void prepare_character_location_data(mmo_charakter& new_char, const nlohmann::json& character); +std::string dump_mmo_charakter(const mmo_charakter& character); + #endif /* CHAR_CREATION_HELPERS_HPP */ diff --git a/src/bot/Brokk/charakters.cpp b/src/bot/Brokk/charakters.cpp index 0e2a0b6..9f8c40e 100644 --- a/src/bot/Brokk/charakters.cpp +++ b/src/bot/Brokk/charakters.cpp @@ -284,15 +284,13 @@ static bool char_db_sql_create(CharDB* self, struct mmo_charakter* charakter) { 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); @@ -671,8 +669,8 @@ static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* char || 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, 35, SQLDT_SHORT, (void*)&charakter->hair, sizeof(charakter->hair)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 36, SQLDT_SHORT, (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)) @@ -731,8 +729,8 @@ static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* char || 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, 13, SQLDT_SHORT, (void*)&charakter->hair, sizeof(charakter->hair)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_SHORT, (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)) diff --git a/src/bot/Brokk/helper_skills.cpp b/src/bot/Brokk/helper_skills.cpp index 15e30bf..ee74f97 100644 --- a/src/bot/Brokk/helper_skills.cpp +++ b/src/bot/Brokk/helper_skills.cpp @@ -10,6 +10,10 @@ #include #include #include +#include + +#include +#include #include #include @@ -17,9 +21,13 @@ #include #include #include +#include +#include #include #include "brokk.hpp" // brokk_config +#include "char_creation_helpers.hpp" +using namespace rathena; /// global defines @@ -48,6 +56,8 @@ typedef struct SkillDBIterator_SQL { int last_skill_id; } SkillDBIterator_SQL; +BotSkillTreeDatabase bot_skill_tree_db; + /// internal functions static bool bot_skill_db_sql_init(SkillDB* self); static void bot_skill_db_sql_destroy(SkillDB* self); @@ -62,6 +72,37 @@ 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); +/* +static bool skill_check(uint16 id) { + if (id == 0 || skill_get_index(id) == 0) + return false; + return true; +} + +#define skill_get(id, var) do {\ + if (!skill_check(id))\ + return 0;\ + return var;\ +} while(0)*/ + +//int skill_get_max(uint16 skill_id) { skill_get(skill_id, skill_db.find(skill_id)->max); } + +/** + * Get skill id from name + * @param name + * @return Skill ID of the skill, or 0 if not found. + * +uint16 skill_name2id(const char* name) { + if (name == nullptr) + return 0; + + for (const auto& it : bot_skill_db) { + if (strcmpi(it.second->name, name) == 0) + return it.first; + } + + return 0; +}*/ /// public constructor SkillDB* skill_db_sql(void) { @@ -491,6 +532,545 @@ static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, c return result; } +/********* +* Skill Tree Yaml Database functions +*********/ + + +std::shared_ptr BotSkillTreeDatabase::get_skill_data(int class_, uint16 skill_id) { + std::shared_ptr tree = this->find(class_); + + if (tree != nullptr) + return util::umap_find(tree->skills, skill_id); + + return nullptr; +} + +const std::string BotSkillTreeDatabase::getDefaultLocation() { + return std::string(db_path) + "/skill_tree.yml"; +} + +/** + * Reads and parses an entry from the skill_tree. + * @param node: YAML node containing the entry. + * @return count of successfully parsed rows + */ +uint64 BotSkillTreeDatabase::parseBodyNode(const ryml::NodeRef& node) { + std::string job_name; + + if (!this->asString(node, "Job", job_name)) + return 0; + + //int64 constant; + std::string job_name_constant = "JOB_" + job_name; + /* + if (!script_get_constant(job_name_constant.c_str(), &constant)) { + this->invalidWarning(node["Job"], "Invalid job %s.\n", job_name.c_str()); + return 0; + } + uint16 job_id = static_cast(constant); + */ + uint16 job_id = convert_jobid_to_number(job_name_constant.c_str()); + std::shared_ptr tree = this->find(job_id); + bool exists = tree != nullptr; + + if (!exists) + tree = std::make_shared(); + + if (this->nodeExists(node, "Inherit")) { + const ryml::NodeRef& InheritNode = node["Inherit"]; + + for (const auto& Inheritit : InheritNode) { + std::string inheritname; + c4::from_chars(Inheritit.key(), &inheritname); + std::string inheritname_constant = "JOB_" + inheritname; + /* + if (!script_get_constant(inheritname_constant.c_str(), &constant)) { + this->invalidWarning(InheritNode[Inheritit.key()], "Invalid job %s.\n", inheritname.c_str()); + return 0; + } + */ + uint16 inherit_job = convert_jobid_to_number(inheritname_constant.c_str()); + bool active; + + if (!this->asBool(InheritNode, inheritname, active)) + return 0; + + //uint16 inherit_job = static_cast(inherited_job); + + if (!active) { + if (exists) + util::vector_erase_if_exists(tree->inherit_job, inherit_job); + } + else { + if (!util::vector_exists(tree->inherit_job, inherit_job)) + tree->inherit_job.push_back(inherit_job); + } + } + } + + if (this->nodeExists(node, "Tree")) { + for (const auto& it : node["Tree"]) { + std::string skill_name; + + if (!this->asString(it, "Name", skill_name)) + return 0; + + //uint16 skill_id = skill_name2id(skill_name.c_str()); + uint16 skill_id = convert_skill_str_to_id(skill_name.c_str()); + + if (skill_id == 0) { + this->invalidWarning(it["Name"], "Invalid skill name \"%s\".\n", skill_name.c_str()); + return 0; + } + /* + if (!skill_get_index(skill_id)) { + this->invalidWarning(it["Name"], "Unable to load skill %s into job %hu's tree.\n", skill_name.c_str(), job_id); + return 0; + } + */ + std::shared_ptr entry; + bool skill_exists = tree->skills.count(skill_id) > 0; + + if (skill_exists) + entry = tree->skills[skill_id]; + else + entry = std::make_shared(); + + entry->skill_id = skill_id; + + uint16 max_lv; + + if (!this->asUInt16(it, "MaxLevel", max_lv)) + return 0; + + if (max_lv > MAX_SKILL_LEVEL) { + this->invalidWarning(it["MaxLevel"], "MaxLevel exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL); + return 0; + } + /* + uint16 skill_lv_max = skill_get_max(skill_id); + + if (max_lv > skill_lv_max) { + this->invalidWarning(it["MaxLevel"], "Skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), max_lv, skill_lv_max); + max_lv = skill_lv_max; + } + */ + // if (max_lv == 0) { // skill lvl 0 removed on loadingFinished (because of inherit) + // if (!skill_exists || entry->skill_id.erase(skill_id) == 0) + // this->invalidWarning(it["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name.c_str(), job_name.c_str()); + // continue; + // } + + entry->max_lv = max_lv; + + if (this->nodeExists(it, "BaseLevel")) { + uint32 baselv; + + if (!this->asUInt32(it, "BaseLevel", baselv)) + return 0; + /* + uint32 baselv_max = bot_job_db.get_maxBaseLv(job_id); + + if (baselv > baselv_max) { + this->invalidWarning(it["BaseLevel"], "Skill %hu's base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n", + skill_id, baselv, job_name.c_str(), baselv_max); + baselv = baselv_max; + } + */ + entry->baselv = baselv; + } + else { + if (!skill_exists) + entry->baselv = 0; + } + + if (this->nodeExists(it, "JobLevel")) { + uint32 joblv; + + if (!this->asUInt32(it, "JobLevel", joblv)) + return 0; + /* + uint32 joblv_max = bot_job_db.get_maxJobLv(job_id); + + if (joblv > joblv_max) { + this->invalidWarning(it["JobLevel"], "Skill %hu's job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n", + skill_id, joblv, job_name.c_str(), joblv_max); + joblv = joblv_max; + } + */ + entry->joblv = joblv; + } + else { + if (!skill_exists) + entry->joblv = 0; + } + + if (this->nodeExists(it, "Requires")) { + for (const auto& Requiresit : it["Requires"]) { + if (!this->nodesExist(Requiresit, { "Name" })) + return 0; + + std::string skill_name_req; + + if (!this->asString(Requiresit, "Name", skill_name_req)) + return 0; + + //uint16 skill_id_req = skill_name2id(skill_name_req.c_str()); + uint16 skill_id_req = convert_skill_str_to_id(skill_name_req.c_str()); + + if (skill_id_req == 0) { + this->invalidWarning(Requiresit["Name"], "Invalid skill name \"%s\".\n", skill_name_req.c_str()); + return 0; + } + + uint16 lv_req; + + if (!this->asUInt16(Requiresit, "Level", lv_req)) + return 0; + + if (lv_req > MAX_SKILL_LEVEL) { + this->invalidWarning(Requiresit["Level"], "Level exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL); + return 0; + } + /* + uint16 lv_req_max = skill_get_max(skill_id_req); + + if (lv_req > lv_req_max) { + this->invalidWarning(it["MaxLevel"], "Required skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), lv_req, lv_req_max); + lv_req = lv_req_max; + } + */ + if (lv_req == 0) { + if (entry->need.erase(skill_id_req) == 0) + this->invalidWarning(Requiresit["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name_req.c_str(), job_name.c_str()); + continue; + } + + entry->need[skill_id_req] = lv_req; + } + } + + if (this->nodeExists(it, "Exclude")) { + bool exclude; + + if (!this->asBool(it, "Exclude", exclude)) + return 0; + + entry->exclude_inherit = exclude; + } + else { + if (!skill_exists) + entry->exclude_inherit = false; + } + + if (!skill_exists) + tree->skills.insert({ skill_id, entry }); + } + } + + if (!exists) + this->put(job_id, tree); + + return true; +} +/* +* Checks if the required skills to a skill are met +*/ +bool check_skill_requirements(int job_id, int skill_id, bot_skill_bundle& skill_bundle) { + + if (is_skill_validation_exception(job_id, skill_id)) { + // Skill is exception for this job -> is not validated + std::string skill_name = convert_skill_id_to_str(skill_id); + ShowNotice("Skill %s is marked as exception to this job (%d)\n",skill_name.c_str(), job_id); + return true; + } + + + std::shared_ptr tree = bot_skill_tree_db.find(job_id); + if (!tree) { + ShowError("Skill ID %d (%s) not found in bot_skill_tree_db\n", skill_id, convert_skill_id_to_str(skill_id)); + return false; + } + + // Check the size of skills before iterating + if (tree->skills.empty()) { + ShowError("Skill tree for ID %d is empty\n", skill_id); + return false; + } + + for (const auto& skill : tree->skills) { + //ShowDebug("Skill Found in tree: %s \n", convert_skill_id_to_str(skill.first)); + if (skill.first == skill_id) { + //ShowDebug("skill max lvl : %d \n", skill.second->max_lv); + bool requirements_met = true; + for (const auto needed_skill : skill.second->need) { + //ShowDebug("requires skill %s on level %d\n", convert_skill_id_to_str(needed_skill.first), needed_skill.second); + bool skill_found = false; + for (const auto bundled_skill : skill_bundle.skills) { + if (needed_skill.first == bundled_skill.skill_id && needed_skill.second <= bundled_skill.level) { + // Skill must be in bundle and at least smaller or same level as required + skill_found = true; + break; + } + } + if (!skill_found) { + requirements_met = false; + break; + } + } + if (requirements_met) { + ShowDebug("All skill requirements fullfilled"); + return true; //no missing skills, requirements are met + } + } + } + ShowDebug("One or more skill requirements not fullfilled"); + return false; //Skill_bundle misses at least a required skill +} + +void init_bot_skill_tree_db() { + bot_skill_tree_db.reload(); +} + +void BotSkillTreeDatabase::loadingFinished() { + std::unordered_map> job_tree; // get the data from skill_tree_db before populate it + + for (auto& data : *this) { + if (data.second->inherit_job.empty()) + continue; + + std::shared_ptr skill_tree = std::make_shared(); + /* + uint32 baselv_max = bot_job_db.get_maxBaseLv(data.first); + uint32 joblv_max = bot_job_db.get_maxJobLv(data.first); + */ + for (const auto& inherit_job : data.second->inherit_job) { + std::shared_ptr tree = this->find(inherit_job); + if (tree == nullptr || tree->skills.empty()) + continue; + + for (const auto& it : tree->skills) { + if (it.second->exclude_inherit) + continue; + if (data.second->skills.count(it.first) > 0) // skill already in the skill tree + continue; + + if (skill_tree->skills.count(it.first) > 0) // replaced by the last inheritance + skill_tree->skills[it.first] = it.second; + else + skill_tree->skills.insert({ it.first, it.second }); + std::shared_ptr skill = skill_tree->skills[it.first]; + /* + if (skill->baselv > baselv_max) { + ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n", + skill_get_name(skill->skill_id), skill->skill_id, skill->baselv, job_name(data.first), baselv_max); + skill->baselv = baselv_max; + } + if (skill->joblv > joblv_max) { + ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n", + skill_get_name(skill->skill_id), skill->skill_id, skill->joblv, job_name(data.first), joblv_max); + skill->joblv = joblv_max; + } + */ + } + } + if (skill_tree != nullptr && !skill_tree->skills.empty()) + job_tree.insert({ data.first, skill_tree }); + } + + if (!job_tree.empty()) { + for (auto& data : *this) { + if (job_tree.count(data.first) == 0) + continue; + data.second->skills.insert(job_tree[data.first]->skills.begin(), job_tree[data.first]->skills.end()); + } + } + + // remove skills with max_lv = 0 + for (const auto& job : *this) { + if (job.second->skills.empty()) + continue; + + auto it = job.second->skills.begin(); + + while (it != job.second->skills.end()) { + if (it->second->max_lv == 0) { + it = job.second->skills.erase(it); + } + else { + it++; + } + } + } + + TypesafeYamlDatabase::loadingFinished(); +} + +std::unordered_map parse_skill_bundle(const nlohmann::json& skill_bundle) { + std::unordered_map parsed_skill_bundle; + + for (const auto& skill : skill_bundle.items()) { + uint16 skill_id = convert_skill_str_to_id(skill.key().c_str()); + if (skill_id < 0) { + ShowError("Invalid skill ID for skill string: %s\n", skill.key().c_str()); + continue; // Skip invalid skills + } + + uint16 level = skill.value(); // Assuming the value represents the skill level + parsed_skill_bundle[skill_id] = level; + } + + return parsed_skill_bundle; +} + +bool validate_skills(uint16 start_job, bot_skill_bundle& skill_bundle) { + for (const auto& skill : skill_bundle.skills) { + int skill_id = skill.skill_id; + uint16 level = skill.level; + + // Check requirements + if (!check_skill_requirements(start_job, skill_id, skill_bundle)) { + std::string skill_name = convert_skill_id_to_str(skill_id); + ShowError("Skill %s(%d) at level %d does not meet requirements\n", skill_name.c_str(),skill_id, level); + return false; // Requirements not met + } + } + + return true; +} + +void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character) { + //Select for the used profile + std::string skill_profile = character["char_skills_profile"]; + + if (skill_profile == "CUSTOM") { + if (!character.contains("skills")) { + throw std::runtime_error("skills data is missing for CUSTOM skills profile."); + } + + const auto& skills = character["skills"]; + + //Initialize the skill bundle + bundle.skills.clear(); + + // Iterate over the skills in the JSON + for (const auto& skill : skills.items()) { + std::string skill_name = skill.key(); + int skill_level = skill.value(); + + // Convert skill name to skill ID + int skill_id = convert_skill_str_to_id(skill_name.c_str()); + if (skill_id < 0) { // Invalid skill ID + ShowWarning("Skill ID %s is Invalid. Skipping...\n", skill_name.c_str()); + continue; // Skip invalid skills + } + + std::string job_str = character["start_job"]; + uint16 related_job = convert_jobid_to_number(job_str.c_str()); + validate_skills(related_job, bundle); + + // Add the skill to the skill bundle + bot_skill new_skill{}; + new_skill.char_id = char_id; // the character id the skill belongs to + new_skill.skill_id = static_cast(skill_id); + new_skill.level = static_cast(skill_level); + new_skill.flag = 0; // Default: 0 for permanent + bundle.skills.push_back(new_skill); + } + + } + else { + // Load skill Profile + std::string job_str = character["start_job"]; + int job_id = convert_jobid_to_number(job_str.c_str()); + std::string job_profile_file_path = "conf/bot_profiles/skill_profiles/" + job_str + "_skills_profile.json"; + nlohmann::json json_skill_profiles; + + // Try to Open skill Profile + std::ifstream skill_profile_file(job_profile_file_path); + if (!skill_profile_file.is_open()) { + ShowWarning("Failed to open skill_profile_file %s. Trying to load DEFAULT instead.", job_profile_file_path.c_str()); + job_profile_file_path = "conf/bot_profiles/skill_profiles/DEFAULT_skills_profile.json"; + skill_profile_file.open(job_profile_file_path); + if (!skill_profile_file.is_open()) { + throw std::runtime_error("Failed to open both job and default profiles."); + } + } + + // Parse Profile Data + try { + skill_profile_file >> json_skill_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what())); + } + + + if (!json_skill_profiles.contains(skill_profile)) { + // If build profile is not found, default to INIT + skill_profile = "INIT"; + } + + const auto& profile = json_skill_profiles[skill_profile]; + + //Initialize the skill bundle + bundle.skills.clear(); + + // Iterate over the skills in the JSON + for (const auto& skill : profile.items()) { + std::string skill_name = skill.key(); + int skill_level = skill.value(); + + // Convert skill name to skill ID + int skill_id = convert_skill_str_to_id(skill_name.c_str()); + if (skill_id < 0) { // Invalid skill ID + ShowWarning("Skill ID %s is Invalid. Skipping...\n", skill_name.c_str()); + continue; // Skip invalid skills + } + + std::string job_str = character["start_job"]; + uint16 related_job = convert_jobid_to_number(job_str.c_str()); + validate_skills(related_job, bundle); + + // Add the skill to the skill bundle + bot_skill new_skill{}; + new_skill.char_id = char_id; // the character id the skill belongs to + new_skill.skill_id = static_cast(skill_id); + new_skill.level = static_cast(skill_level); + new_skill.flag = 0; // Default: 0 for permanent + bundle.skills.push_back(new_skill); + } + } + +} + +bool is_skill_validation_exception(int job_id, int skill_id) { + // Usually there should not be any exceptions for validation but in case you want to setup bots that have permanent soullink skills active or other temporaö skills, they should be added here + // A Exception is only needed if a skill should be added that is not present in the base skill tree but would use a slot in the jobs skill window like custom/item skills or soullink skills + + // Map job IDs to arrays of exception skill IDs + static const std::unordered_map> skill_map = { + {JOB_DANCER, {BA_WHISTLE, BA_ASSASSINCROSS, BA_POEMBRAGI, BA_APPLEIDUN}}, // Dancer exceptions + {JOB_KNIGHT, {KN_ONEHAND}}, // Knight exceptions + // Add more job-specific exceptions here + }; + + // Check if the job_id exists in the map + auto it = skill_map.find(job_id); + if (it != skill_map.end()) { + // Check if the skill_id exists in the exception list for the job_id + const auto& exception_skills = it->second; + if (std::find(exception_skills.begin(), exception_skills.end(), skill_id) != exception_skills.end()) { + return true; + } + } + + // If no match is found + return false; +} + + /**********+ * Below this point there are only large converter functions. ************/ diff --git a/src/bot/Brokk/helper_skills.hpp b/src/bot/Brokk/helper_skills.hpp index 9910884..56b1bec 100644 --- a/src/bot/Brokk/helper_skills.hpp +++ b/src/bot/Brokk/helper_skills.hpp @@ -6,9 +6,16 @@ #ifndef HELPER_SKILLS_HPP #define HELPER_SKILLS_HPP +#include "json.hpp" + #include +#include //#include // ACCOUNT_REG2_NUM, WEB_AUTH_TOKEN_LENGTH #include +#include +#include +#include + typedef struct SkillDB SkillDB; typedef struct SkillDBIterator SkillDBIterator; @@ -24,6 +31,10 @@ struct bot_skill { int flag; // Name of the player character that spawned/summoned the bot [Default: Brokk for generated bots) }; +struct bot_skill_bundle { + std::vector skills; +}; + struct SkillDBIterator { /// Destroys this iterator, releasing all allocated memory (including itself). @@ -113,1838 +124,38 @@ struct SkillDB { 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, +bool check_skill_requirements(int job_id, int skill_id, bot_skill_bundle& skill_bundle); +bool is_skill_validation_exception(int job_id, int skill_id); + +struct bot_skill_tree_entry { + uint16 skill_id, max_lv; + uint32 baselv, joblv; + std::unordered_map need; /// skill_id, skill_lv + bool exclude_inherit; // exclude the skill from inherit when loading the table +}; - 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, +struct bot_skill_tree { + std::vector inherit_job; + std::unordered_map> skills; /// skill_id, entry +}; - NW_THE_VIGILANTE_AT_NIGHT_GUN_GATLING, - NW_THE_VIGILANTE_AT_NIGHT_GUN_SHOTGUN, - SS_FUUMAKOUCHIKU_BLASTING, +class BotSkillTreeDatabase : public TypesafeYamlDatabase { +public: + BotSkillTreeDatabase() : TypesafeYamlDatabase("SKILL_TREE_DB", 1) { - 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, + const std::string getDefaultLocation() override; + uint64 parseBodyNode(const ryml::NodeRef& node) override; + void loadingFinished() override; - 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, + // Additional + std::shared_ptr get_skill_data(int class_, uint16 skill_id); +}; +extern BotSkillTreeDatabase bot_skill_tree_db; - 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, +void init_bot_skill_tree_db(); +bool validate_skills(uint16 start_job, const bot_skill_bundle& skill_bundle); +void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character); - ABR_BATTLE_BUSTER = 8601, - ABR_DUAL_CANNON_FIRE, - ABR_NET_REPAIR, - ABR_NET_SUPPORT, - ABR_INFINITY_BUSTER, -}; -*/ #endif /* HELPER_SKILLS_HPP */