From 602f3edd679218025b669a540b1c54b2f145c089 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 2 Feb 2025 22:07:24 +0100 Subject: [PATCH] Add item/equip creation/application and management to brokk --- src/bot/Brokk/brokk.cpp | 30 +- src/bot/Brokk/char_creation_helpers.cpp | 372 ++-------- src/bot/Brokk/charakters.cpp | 99 --- src/bot/Brokk/charakters.hpp | 1 - src/bot/Brokk/helper_inventory.cpp | 865 +++++++++++++++++------- src/bot/Brokk/helper_inventory.hpp | 26 +- src/bot/Brokk/helper_skills.cpp | 37 +- src/bot/Brokk/helper_skills.hpp | 4 +- 8 files changed, 739 insertions(+), 695 deletions(-) diff --git a/src/bot/Brokk/brokk.cpp b/src/bot/Brokk/brokk.cpp index 90d18d1..adb5434 100644 --- a/src/bot/Brokk/brokk.cpp +++ b/src/bot/Brokk/brokk.cpp @@ -169,7 +169,7 @@ int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, con // Check if the bot account already exists if (bot_accounts->load_num(bot_accounts, &bot_acc, acc.account_id)) { - ShowWarning("Attempt to create an already existing bot account (account: %s, sex: %c). Skipping...\n", acc.account_id, sex); + ShowWarning("Attempt to create an already existing bot account (account: %u, sex: %c). Skipping...\n", acc.account_id, sex); return 1; // Account exists } @@ -530,7 +530,7 @@ void brokk_set_defaults() { void bot_account_cleanup() { - ShowStatus("Start bot deletion according to settings...\n"); + ShowStatus("Start bot deletion according to settings...\n\n"); struct mmo_bot_account bot_acc; BotAccountDBIterator* iter_bots = bot_accounts->iterator(bot_accounts); @@ -944,7 +944,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) { // Close file after done with it bot_accounts_file.close(); - ShowInfo("Starting to create bot accounts...\n"); + ShowInfo("Starting to create bot accounts...\n\n"); // Iterate over each bot entry in the JSON file for (const auto& bot : json_bot_accounts) { @@ -1069,7 +1069,6 @@ bool BrokkServer::initialize(int argc, char* argv[]) { // Step6: Items // Prepare items inside a item bundle ShowInfo("Preparing inventory data for bot character %s ...\n", char_name.c_str()); - bot_item_bundle item_bundle{}; prepare_item_bundle(item_bundle, new_char_id, json_character); @@ -1089,8 +1088,27 @@ bool BrokkServer::initialize(int argc, char* argv[]) { ShowStatus("%d Items successfully handed out to character (name: %s, char_id: %d, sex: %c)\n", item_bundle.items.size(), new_char.name, new_char.char_id, new_char.sex); //Step5: Equipment - //ShowInfo("Preparing equipment for bot character %s ...\n", char_name.c_str()); - + ShowInfo("Preparing equipment for bot character %s ...\n", char_name.c_str()); + + bot_item_bundle equip_bundle{}; + prepare_equipment_bundle(equip_bundle, new_char_id, json_character); + + // All equip related data is collected and can now be saved + try { + + if (!brokk_apply_item_loadout(equip_bundle, new_char_id)) { + ShowError("Failed to create inventory entry for: %s\n", new_char.name); + continue; + } + } + catch (const std::exception& e) { + ShowError("item creation for '%s 'failed: %s\n", new_char.name, e.what()); + continue; + } + + ShowStatus("%d Gear-items successfully equiped to character (name: %s, char_id: %d, sex: %c)\n", equip_bundle.items.size(), new_char.name, new_char.char_id, new_char.sex); + + ShowStatus("Bot-Character creation and administration completed (name: %s, char_id: %d, sex: %c)\n\n", new_char.name, new_char.char_id, new_char.sex); } diff --git a/src/bot/Brokk/char_creation_helpers.cpp b/src/bot/Brokk/char_creation_helpers.cpp index 9d24aed..aba564f 100644 --- a/src/bot/Brokk/char_creation_helpers.cpp +++ b/src/bot/Brokk/char_creation_helpers.cpp @@ -230,304 +230,6 @@ void BotStatPointDatabase::loadingFinished() { * Job Data Functions */ -/*==================================================== - * This function return the name of the job (by [Yor]) - *---------------------------------------------------- -const char* job_name(int class_) -{ - switch (class_) { - case JOB_NOVICE: - case JOB_SWORDMAN: - case JOB_MAGE: - case JOB_ARCHER: - case JOB_ACOLYTE: - case JOB_MERCHANT: - case JOB_THIEF: - return msg_txt(nullptr, 550 - JOB_NOVICE + class_); - - case JOB_KNIGHT: - case JOB_PRIEST: - case JOB_WIZARD: - case JOB_BLACKSMITH: - case JOB_HUNTER: - case JOB_ASSASSIN: - return msg_txt(nullptr, 557 - JOB_KNIGHT + class_); - - case JOB_KNIGHT2: - return msg_txt(nullptr, 557); - - case JOB_CRUSADER: - case JOB_MONK: - case JOB_SAGE: - case JOB_ROGUE: - case JOB_ALCHEMIST: - case JOB_BARD: - case JOB_DANCER: - return msg_txt(nullptr, 563 - JOB_CRUSADER + class_); - - case JOB_CRUSADER2: - return msg_txt(nullptr, 563); - - case JOB_WEDDING: - case JOB_SUPER_NOVICE: - case JOB_GUNSLINGER: - case JOB_NINJA: - case JOB_XMAS: - return msg_txt(nullptr, 570 - JOB_WEDDING + class_); - - case JOB_SUMMER: - case JOB_SUMMER2: - return msg_txt(nullptr, 621); - - case JOB_HANBOK: - return msg_txt(nullptr, 694); - - case JOB_OKTOBERFEST: - return msg_txt(nullptr, 696); - - case JOB_NOVICE_HIGH: - case JOB_SWORDMAN_HIGH: - case JOB_MAGE_HIGH: - case JOB_ARCHER_HIGH: - case JOB_ACOLYTE_HIGH: - case JOB_MERCHANT_HIGH: - case JOB_THIEF_HIGH: - return msg_txt(nullptr, 575 - JOB_NOVICE_HIGH + class_); - - case JOB_LORD_KNIGHT: - case JOB_HIGH_PRIEST: - case JOB_HIGH_WIZARD: - case JOB_WHITESMITH: - case JOB_SNIPER: - case JOB_ASSASSIN_CROSS: - return msg_txt(nullptr, 582 - JOB_LORD_KNIGHT + class_); - - case JOB_LORD_KNIGHT2: - return msg_txt(nullptr, 582); - - case JOB_PALADIN: - case JOB_CHAMPION: - case JOB_PROFESSOR: - case JOB_STALKER: - case JOB_CREATOR: - case JOB_CLOWN: - case JOB_GYPSY: - return msg_txt(nullptr, 588 - JOB_PALADIN + class_); - - case JOB_PALADIN2: - return msg_txt(nullptr, 588); - - case JOB_BABY: - case JOB_BABY_SWORDMAN: - case JOB_BABY_MAGE: - case JOB_BABY_ARCHER: - case JOB_BABY_ACOLYTE: - case JOB_BABY_MERCHANT: - case JOB_BABY_THIEF: - return msg_txt(nullptr, 595 - JOB_BABY + class_); - - case JOB_BABY_KNIGHT: - case JOB_BABY_PRIEST: - case JOB_BABY_WIZARD: - case JOB_BABY_BLACKSMITH: - case JOB_BABY_HUNTER: - case JOB_BABY_ASSASSIN: - return msg_txt(nullptr, 602 - JOB_BABY_KNIGHT + class_); - - case JOB_BABY_KNIGHT2: - return msg_txt(nullptr, 602); - - case JOB_BABY_CRUSADER: - case JOB_BABY_MONK: - case JOB_BABY_SAGE: - case JOB_BABY_ROGUE: - case JOB_BABY_ALCHEMIST: - case JOB_BABY_BARD: - case JOB_BABY_DANCER: - return msg_txt(nullptr, 608 - JOB_BABY_CRUSADER + class_); - - case JOB_BABY_CRUSADER2: - return msg_txt(nullptr, 608); - - case JOB_SUPER_BABY: - return msg_txt(nullptr, 615); - - case JOB_TAEKWON: - return msg_txt(nullptr, 616); - case JOB_STAR_GLADIATOR: - case JOB_STAR_GLADIATOR2: - return msg_txt(nullptr, 617); - case JOB_SOUL_LINKER: - return msg_txt(nullptr, 618); - - case JOB_GANGSI: - case JOB_DEATH_KNIGHT: - case JOB_DARK_COLLECTOR: - return msg_txt(nullptr, 622 - JOB_GANGSI + class_); - - case JOB_RUNE_KNIGHT: - case JOB_WARLOCK: - case JOB_RANGER: - case JOB_ARCH_BISHOP: - case JOB_MECHANIC: - case JOB_GUILLOTINE_CROSS: - return msg_txt(nullptr, 625 - JOB_RUNE_KNIGHT + class_); - - case JOB_RUNE_KNIGHT_T: - case JOB_WARLOCK_T: - case JOB_RANGER_T: - case JOB_ARCH_BISHOP_T: - case JOB_MECHANIC_T: - case JOB_GUILLOTINE_CROSS_T: - return msg_txt(nullptr, 681 - JOB_RUNE_KNIGHT_T + class_); - - case JOB_ROYAL_GUARD: - case JOB_SORCERER: - case JOB_MINSTREL: - case JOB_WANDERER: - case JOB_SURA: - case JOB_GENETIC: - case JOB_SHADOW_CHASER: - return msg_txt(nullptr, 631 - JOB_ROYAL_GUARD + class_); - - case JOB_ROYAL_GUARD_T: - case JOB_SORCERER_T: - case JOB_MINSTREL_T: - case JOB_WANDERER_T: - case JOB_SURA_T: - case JOB_GENETIC_T: - case JOB_SHADOW_CHASER_T: - return msg_txt(nullptr, 687 - JOB_ROYAL_GUARD_T + class_); - - case JOB_RUNE_KNIGHT2: - case JOB_RUNE_KNIGHT_T2: - return msg_txt(nullptr, 625); - - case JOB_ROYAL_GUARD2: - case JOB_ROYAL_GUARD_T2: - return msg_txt(nullptr, 631); - - case JOB_RANGER2: - case JOB_RANGER_T2: - return msg_txt(nullptr, 627); - - case JOB_MECHANIC2: - case JOB_MECHANIC_T2: - return msg_txt(nullptr, 629); - - case JOB_BABY_RUNE_KNIGHT: - case JOB_BABY_WARLOCK: - case JOB_BABY_RANGER: - case JOB_BABY_ARCH_BISHOP: - case JOB_BABY_MECHANIC: - case JOB_BABY_GUILLOTINE_CROSS: - case JOB_BABY_ROYAL_GUARD: - case JOB_BABY_SORCERER: - case JOB_BABY_MINSTREL: - case JOB_BABY_WANDERER: - case JOB_BABY_SURA: - case JOB_BABY_GENETIC: - case JOB_BABY_SHADOW_CHASER: - return msg_txt(nullptr, 638 - JOB_BABY_RUNE_KNIGHT + class_); - - case JOB_BABY_RUNE_KNIGHT2: - return msg_txt(nullptr, 638); - - case JOB_BABY_ROYAL_GUARD2: - return msg_txt(nullptr, 644); - - case JOB_BABY_RANGER2: - return msg_txt(nullptr, 640); - - case JOB_BABY_MECHANIC2: - return msg_txt(nullptr, 642); - - case JOB_SUPER_NOVICE_E: - case JOB_SUPER_BABY_E: - return msg_txt(nullptr, 651 - JOB_SUPER_NOVICE_E + class_); - - case JOB_KAGEROU: - case JOB_OBORO: - return msg_txt(nullptr, 653 - JOB_KAGEROU + class_); - - case JOB_REBELLION: - return msg_txt(nullptr, 695); - - case JOB_SUMMONER: - return msg_txt(nullptr, 697); - case JOB_BABY_SUMMONER: - return msg_txt(nullptr, 698); - case JOB_BABY_NINJA: - return msg_txt(nullptr, 699); - - case JOB_BABY_KAGEROU: - case JOB_BABY_OBORO: - case JOB_BABY_TAEKWON: - case JOB_BABY_STAR_GLADIATOR: - case JOB_BABY_SOUL_LINKER: - case JOB_BABY_GUNSLINGER: - case JOB_BABY_REBELLION: - return msg_txt(nullptr, 753 - JOB_BABY_KAGEROU + class_); - - case JOB_BABY_STAR_GLADIATOR2: - return msg_txt(nullptr, 756); - - case JOB_STAR_EMPEROR: - case JOB_SOUL_REAPER: - case JOB_BABY_STAR_EMPEROR: - case JOB_BABY_SOUL_REAPER: - return msg_txt(nullptr, 782 - JOB_STAR_EMPEROR + class_); - - case JOB_STAR_EMPEROR2: - return msg_txt(nullptr, 782); - - case JOB_BABY_STAR_EMPEROR2: - return msg_txt(nullptr, 784); - - case JOB_DRAGON_KNIGHT: - case JOB_MEISTER: - case JOB_SHADOW_CROSS: - case JOB_ARCH_MAGE: - case JOB_CARDINAL: - case JOB_WINDHAWK: - case JOB_IMPERIAL_GUARD: - case JOB_BIOLO: - case JOB_ABYSS_CHASER: - case JOB_ELEMENTAL_MASTER: - case JOB_INQUISITOR: - case JOB_TROUBADOUR: - case JOB_TROUVERE: - return msg_txt(nullptr, 800 - JOB_DRAGON_KNIGHT + class_); - - case JOB_WINDHAWK2: - return msg_txt(nullptr, 805); - - case JOB_MEISTER2: - return msg_txt(nullptr, 801); - - case JOB_DRAGON_KNIGHT2: - return msg_txt(nullptr, 800); - - case JOB_IMPERIAL_GUARD2: - return msg_txt(nullptr, 806); - - case JOB_SKY_EMPEROR: - case JOB_SOUL_ASCETIC: - case JOB_SHINKIRO: - case JOB_SHIRANUI: - case JOB_NIGHT_WATCH: - case JOB_HYPER_NOVICE: - case JOB_SPIRIT_HANDLER: - return msg_txt(nullptr, 813 - JOB_SKY_EMPEROR + class_); - - case JOB_SKY_EMPEROR2: - return msg_txt(nullptr, 813); - - default: - return msg_txt(nullptr, 655); - } -}*/ - /** * Returns max base level for this character's class. * @param job_id: Player's class @@ -677,11 +379,11 @@ void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& uint64 job_exp = 0; if (new_char.base_level > 1) { - base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level - 1); + base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level); } if (new_char.job_level > 1) { - job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level - 1); + job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level); } new_char.base_exp = base_exp; @@ -689,11 +391,10 @@ void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& 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_hp = pc_calc_basehp(new_char.base_level, new_char.class_); + new_char.hp = new_char.max_hp; + new_char.max_sp = pc_calc_basesp(new_char.base_level, new_char.class_); + new_char.sp = new_char.max_sp; new_char.max_ap = 0; new_char.ap = 0; @@ -728,7 +429,7 @@ void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& if (!build_profile_file.is_open()) { throw std::runtime_error("Failed to open both job and default profiles."); } - } + } // Parse Profile Data try { @@ -737,29 +438,35 @@ void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& 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; + + new_char.max_hp = pc_calc_basehp(new_char.base_level, new_char.class_); + new_char.hp = new_char.max_hp; + new_char.max_sp = pc_calc_basesp(new_char.base_level, new_char.class_); + new_char.sp = new_char.max_sp; + new_char.max_ap = 0; + new_char.ap = 0; //Set stats new_char.str = profile["str"]; @@ -775,6 +482,7 @@ void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& new_char.spl = profile["spl"]; new_char.con = profile["con"]; new_char.crt = profile["crt"]; + } //ShowDebug("Character: \n%s",dump_mmo_charakter(new_char)); } @@ -1164,8 +872,18 @@ const std::string BotJobDatabase::getDefaultLocation() { */ t_exp BotJobDatabase::get_baseExp(uint16 job_id, uint32 level) { std::shared_ptr job = bot_job_db.find(job_id); + if (job) { + if (level > 1) { + return job->base_exp[level - 1]; + } + else { + return 0; + } - return job ? job->base_exp[level - 1] : 0; + } + else { + return 0; + } } /** @@ -1176,8 +894,18 @@ t_exp BotJobDatabase::get_baseExp(uint16 job_id, uint32 level) { */ t_exp BotJobDatabase::get_jobExp(uint16 job_id, uint32 level) { std::shared_ptr job = bot_job_db.find(job_id); + if (job) { + if (level > 1) { + return job->job_exp[level - 1]; + } + else { + return 0; + } - return job ? job->job_exp[level - 1] : 0; + } + else { + return 0; + } } /** @@ -1287,25 +1015,25 @@ uint64 BotJobDatabase::parseBodyNode(const ryml::NodeRef& node) { for (const auto& aspdit : aspdNode) { std::string weapon; c4::from_chars(aspdit.key(), &weapon); - std::string weapon_constant = "W_" + weapon; - int64 constant = convert_weaponid_to_number(weapon_constant.c_str()); + //std::string weapon_constant = "W_" + weapon; + //int64 constant = convert_weaponid_to_number(weapon_constant.c_str()); //if (!script_get_constant(weapon_constant.c_str(), &constant)) { // this->invalidWarning(aspdNode["BaseASPD"], "Unknown weapon type %s specified for %s, skipping.\n", weapon.c_str(), job_name.c_str()); // continue; //} - if (constant < BOT_W_FIST || constant >= max) { - this->invalidWarning(aspdNode["BaseASPD"], "Invalid weapon type %s specified for %s, skipping.\n", weapon.c_str(), job_name.c_str()); - continue; - } + //if (constant < BOT_W_FIST || constant >= max) { + // this->invalidWarning(aspdNode["BaseASPD"], "Invalid weapon type %s specified for %s, skipping.\n", weapon.c_str(), job_name.c_str()); + // continue; + //} - int16 aspd; + //int16 aspd; - if (!this->asInt16(aspdNode, weapon.c_str(), aspd)) - return 0; + //if (!this->asInt16(aspdNode, weapon.c_str(), aspd)) + // return 0; - job->aspd_base[static_cast(constant)] = aspd; + //job->aspd_base[static_cast(constant)] = aspd; } } else { diff --git a/src/bot/Brokk/charakters.cpp b/src/bot/Brokk/charakters.cpp index 9f8c40e..b502580 100644 --- a/src/bot/Brokk/charakters.cpp +++ b/src/bot/Brokk/charakters.cpp @@ -1004,102 +1004,3 @@ int convert_jobid_to_number(const char* job_id) { return -1; // Return an error value if the job ID is unknown } } - -/* -* Weapon Types -*/ -enum weapon_type : uint8 { - W_FIST, //Bare hands - W_DAGGER, //1 - W_1HSWORD, //2 - W_2HSWORD, //3 - W_1HSPEAR, //4 - W_2HSPEAR, //5 - W_1HAXE, //6 - W_2HAXE, //7 - W_MACE, //8 - W_2HMACE, //9 (unused) - W_STAFF, //10 - W_BOW, //11 - W_KNUCKLE, //12 - W_MUSICAL, //13 - W_WHIP, //14 - W_BOOK, //15 - W_KATAR, //16 - W_REVOLVER, //17 - W_RIFLE, //18 - W_GATLING, //19 - W_SHOTGUN, //20 - W_GRENADE, //21 - W_HUUMA, //22 - W_2HSTAFF, //23 - MAX_WEAPON_TYPE, - // dual-wield constants - W_DOUBLE_DD, // 2 daggers - W_DOUBLE_SS, // 2 swords - W_DOUBLE_AA, // 2 axes - W_DOUBLE_DS, // dagger + sword - W_DOUBLE_DA, // dagger + axe - W_DOUBLE_SA, // sword + axe - MAX_WEAPON_TYPE_ALL, - W_SHIELD = MAX_WEAPON_TYPE, -}; - -#define WEAPON_TYPE_ALL ((1< weapon_map = { - {"W_FIST", W_FIST}, //Bare hands - {"W_DAGGER", W_DAGGER}, //1 - {"W_1HSWORD", W_1HSWORD}, //2 - {"W_2HSWORD", W_2HSWORD}, //3 - {"W_1HSPEAR", W_1HSPEAR}, //4 - {"W_2HSPEAR", W_2HSPEAR}, //5 - {"W_1HAXE", W_1HAXE}, //6 - {"W_2HAXE", W_2HAXE}, //7 - {"W_MACE", W_MACE}, //8 - {"W_2HMACE", W_2HMACE}, //9 (unused) - {"W_STAFF", W_STAFF}, //10 - {"W_BOW", W_BOW}, //11 - {"W_KNUCKLE", W_KNUCKLE}, //12 - {"W_MUSICAL", W_MUSICAL}, //13 - {"W_WHIP", W_WHIP}, //14 - {"W_BOOK", W_BOOK}, //15 - {"W_KATAR", W_KATAR}, //16 - {"W_REVOLVER", W_REVOLVER}, //17 - {"W_RIFLE", W_RIFLE}, //18 - {"W_GATLING", W_GATLING}, //19 - {"W_SHOTGUN", W_SHOTGUN}, //20 - {"W_GRENADE", W_GRENADE}, //21 - {"W_HUUMA", W_HUUMA}, //22 - {"W_2HSTAFF", W_2HSTAFF}, //23 - {"MAX_WEAPON_TYPE", MAX_WEAPON_TYPE}, - // dual-wield constants - {"W_DOUBLE_DD", W_DOUBLE_DD}, // 2 daggers - {"W_DOUBLE_SS", W_DOUBLE_SS}, // 2 swords - {"W_DOUBLE_AA", W_DOUBLE_AA}, // 2 axes - {"W_DOUBLE_DS", W_DOUBLE_DS}, // dagger + sword - {"W_DOUBLE_DA", W_DOUBLE_DA}, // dagger + axe - {"W_DOUBLE_SA", W_DOUBLE_SA}, // sword + axe - {"MAX_WEAPON_TYPE_ALL", MAX_WEAPON_TYPE_ALL}, - {"W_SHIELD", W_SHIELD}, - // Add other job mappings here as needed - }; - - // Look up the job_id in the map - std::string weapon_str = weapon_id; - // Convert to upper case - for (int index = 0; index < weapon_str.size(); index++) { - weapon_str.at(index) = toupper(weapon_str.at(index)); - } - auto it = weapon_map.find(weapon_str); - if (it != weapon_map.end()) { - return it->second; // Return the corresponding enum value - } - else { - ShowError("Unknown weapon ID: %s\n", weapon_id); - return -1; // Return an error value if the weapon ID is unknown - } -} diff --git a/src/bot/Brokk/charakters.hpp b/src/bot/Brokk/charakters.hpp index 03bdd8d..e9e905c 100644 --- a/src/bot/Brokk/charakters.hpp +++ b/src/bot/Brokk/charakters.hpp @@ -211,6 +211,5 @@ struct CharDB { //void mmo_save_global_accreg(BotAccountDB* self, int fd, uint32 account_id, uint32 char_id); int convert_jobid_to_number(const char* job_id); -int convert_weaponid_to_number(const char* weapon_id); #endif /* CHARAKTERS_HPP */ diff --git a/src/bot/Brokk/helper_inventory.cpp b/src/bot/Brokk/helper_inventory.cpp index e4ba2ad..2d6a2b5 100644 --- a/src/bot/Brokk/helper_inventory.cpp +++ b/src/bot/Brokk/helper_inventory.cpp @@ -34,6 +34,11 @@ using namespace rathena; using json = nlohmann::json; /// global defines +// Global cache for item AegisName-to-ID mapping +static json cached_itemdb_aegisname_to_id; +static json cached_itemdb_id_to_aegisname; +static json cached_itemdb_id_to_name; +static bool itemdbs_loaded = false; // Flag to track if data is loaded /// internal structure typedef struct InventoryDB_SQL { @@ -286,23 +291,7 @@ static bool bot_inv_item_db_sql_create(InventoryDB* self, struct bot_inv_item* i * @param char_id: id of char, the item belongs to * @param inventory_id: if of item to delete * @return true if successful, false if something has failed - -static bool bot_inv_item_db_sql_remove(InventoryDB* self, const uint32 char_id, const uint32 inventory_id) { - InventoryDB_SQL* db = (InventoryDB_SQL*)self; - Sql* sql_handle = db->bot_inventory; - bool result = false; - - if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") - || SQL_SUCCESS != Sql_Query(sql_handle, "DELETE FROM `%s` WHERE `char_id` = %d AND `id` = %d", db->inventory_db, char_id, inventory_id) - ) - Sql_ShowDebug(sql_handle); - else - result = true; - - result &= ( SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK") ); - - return result; -}*/ + */ static bool bot_inv_item_db_sql_remove(InventoryDB* self, const uint32 char_id, const uint32 inventory_id) { InventoryDB_SQL* db = (InventoryDB_SQL*)self; Sql* sql_handle = db->bot_inventory; @@ -395,27 +384,49 @@ void dump_item_bundle(const bot_item_bundle& item_bundle) { ShowDebug("End of item bundle dump.\n"); } +/* +* Weapon Types +*/ +enum weapon_type : uint8 { + W_FIST, //Bare hands + W_DAGGER, //1 + W_1HSWORD, //2 + W_2HSWORD, //3 + W_1HSPEAR, //4 + W_2HSPEAR, //5 + W_1HAXE, //6 + W_2HAXE, //7 + W_MACE, //8 + W_2HMACE, //9 (unused) + W_STAFF, //10 + W_BOW, //11 + W_KNUCKLE, //12 + W_MUSICAL, //13 + W_WHIP, //14 + W_BOOK, //15 + W_KATAR, //16 + W_REVOLVER, //17 + W_RIFLE, //18 + W_GATLING, //19 + W_SHOTGUN, //20 + W_GRENADE, //21 + W_HUUMA, //22 + W_2HSTAFF, //23 + MAX_WEAPON_TYPE, + // dual-wield constants + W_DOUBLE_DD, // 2 daggers + W_DOUBLE_SS, // 2 swords + W_DOUBLE_AA, // 2 axes + W_DOUBLE_DS, // dagger + sword + W_DOUBLE_DA, // dagger + axe + W_DOUBLE_SA, // sword + axe + MAX_WEAPON_TYPE_ALL, + W_SHIELD = MAX_WEAPON_TYPE, +}; + +#define WEAPON_TYPE_ALL ((1<vtable.destroy = &bot_inv_item_db_sql_iter_destroy; - iter->vtable.next = &bot_inv_item_db_sql_iter_next; - - // fill data - iter->db = db; - iter->char_id = char_id; - iter->last_inv_item_id = -1; - return &iter->vtable; -} */ static InventoryDBIterator* bot_inv_item_db_sql_iterator(InventoryDB* self, const uint32 char_id) { InventoryDB_SQL* db = (InventoryDB_SQL*)self; @@ -458,43 +469,6 @@ static void bot_inv_item_db_sql_iter_destroy(InventoryDBIterator* self) { aFree(iter); } -/** - * Fetches the next inv item entry in the database. - * @param self: pointer to db iterator - * @param inventory_id: id of the inventory table the item belongs to - * @return true if next inventory entry found and filled, false if something has failed - -static bool bot_inv_item_db_sql_iter_next(InventoryDBIterator* self, struct bot_inv_item* item) { - InventoryDBIterator_SQL* iter = (InventoryDBIterator_SQL*)self; - InventoryDB_SQL* db = iter->db; - Sql* sql_handle = db->bot_inventory; - char* data; - - // get next inventory ID - if( SQL_ERROR == Sql_Query(sql_handle, "SELECT `id` FROM `%s` WHERE `char_id` = %d AND `id` > '%d' ORDER BY `id` ASC LIMIT 1", - db->inventory_db, iter->char_id, iter->last_inv_item_id) ) - { - Sql_ShowDebug(sql_handle); - return false; - } - - if( SQL_SUCCESS == Sql_NextRow(sql_handle) && - SQL_SUCCESS == Sql_GetData(sql_handle, 0, &data, nullptr) && - data != nullptr ) - {// get item data - uint32 inventory_id = atoi(data); - item->inventory_id = inventory_id; - - if( bot_inv_item_db_fromsql(db, item) ) - { - iter->last_inv_item_id = inventory_id; - Sql_FreeResult(sql_handle); - return true; - } - } - Sql_FreeResult(sql_handle); - return false; -} */ static bool bot_inv_item_db_sql_iter_next(InventoryDBIterator* self, struct bot_inv_item* item) { InventoryDBIterator_SQL* iter = (InventoryDBIterator_SQL*)self; InventoryDB_SQL* db = iter->db; @@ -549,80 +523,6 @@ static bool bot_inv_item_db_sql_iter_next(InventoryDBIterator* self, struct bot_ return false; } -/** - * Fetch a struct bot_inv_item from sql. - * @param db: pointer to db - * @param item: pointer of bot_inv_item to fill - * @param char_id: id of character, item belong to - * @param item_id: id of item to take data from - * @return true if successful, false if something has failed - -static bool bot_inv_item_db_fromsql(InventoryDB_SQL* db, struct bot_inv_item* item) { - Sql* sql_handle = db->bot_inventory; - char* data; - - // retrieve login entry for the specified account - if( SQL_ERROR == Sql_Query(sql_handle, -#ifdef VIP_ENABLE - "SELECT `id`, `char_id`, `nameid`, `amount`, `equip`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `enchantgrade` FROM `%s` WHERE `id` = %d", -#else - "SELECT `id`, `char_id`, `nameid`, `amount`, `equip`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `enchantgrade` FROM `%s` WHERE `id` = %d", -#endif - db->inventory_db, item->inventory_id ) - ) { - Sql_ShowDebug(sql_handle); - return false; - } - - if( SQL_SUCCESS != Sql_NextRow(sql_handle) ) - {// no such entry - Sql_FreeResult(sql_handle); - return false; - } - - Sql_GetData(sql_handle, 0, &data, nullptr); item->inventory_id = atoi(data); - Sql_GetData(sql_handle, 1, &data, nullptr); item->char_id = atoi(data); - Sql_GetData(sql_handle, 2, &data, nullptr); item->item_id = atoi(data); - Sql_GetData(sql_handle, 3, &data, nullptr); item->amount = atoi(data); - Sql_GetData(sql_handle, 4, &data, nullptr); item->equip = atoi(data); - Sql_GetData(sql_handle, 5, &data, nullptr); item->refine = atoi(data); - Sql_GetData(sql_handle, 6, &data, nullptr); item->attribute = atoi(data); - Sql_GetData(sql_handle, 7, &data, nullptr); item->card0 = atoi(data); - Sql_GetData(sql_handle, 8, &data, nullptr); item->card1 = atoi(data); - Sql_GetData(sql_handle, 9, &data, nullptr); item->card2 = atoi(data); - Sql_GetData(sql_handle, 10, &data, nullptr); item->card3 = atoi(data); - Sql_GetData(sql_handle, 11, &data, nullptr); item->enchantgrade = atoi(data); - -//endif - Sql_FreeResult(sql_handle); - - //Other fields are more or less irrelevant for bot purposes -> therefor they are pre-filled with default values - item->identify = 1; //identify = 1 means is successfully identified - item->expire_time = 0; //Dont expire - item->favorite = 0; - item->bound = 0; - item->unique_id = 0; - item->equip_switch = 0; - - item->option_id0 = 0; - item->option_parm0 = 0; - item->option_val0 = 0; - item->option_id1 = 0; - item->option_parm1 = 0; - item->option_val1 = 0; - item->option_id2 = 0; - item->option_parm2 = 0; - item->option_val2 = 0; - item->option_id3 = 0; - item->option_parm3 = 0; - item->option_val3 = 0; - item->option_id4 = 0; - item->option_parm4 = 0; - item->option_val4 = 0; - - return true; -} */ - static bool bot_inv_item_db_fromsql(InventoryDB_SQL* db, struct bot_inv_item* item) { Sql* sql_handle = db->bot_inventory; char* data; @@ -826,125 +726,80 @@ void set_bot_inv_item_defaults(bot_inv_item& item) { item.enchantgrade = 0; } -uint32 convert_item_aegisname_to_id(const char* aegisname) +void set_bot_equip_item_defaults(bot_inv_item& item) { - // Open the mapping JSON file - std::ifstream bot_itemdb_aegisname_to_id_file("conf/bot_helper_jsons/itemdb_converter_aegisname_to_id.json"); - if (!bot_itemdb_aegisname_to_id_file.is_open()) { - ShowError("Failed to open itemdb_converter_aegisname_to_id.json\n"); - return 0; - } - - // Parse the JSON file - json json_itemdb_aegisname_to_id; - try { - bot_itemdb_aegisname_to_id_file >> json_itemdb_aegisname_to_id; - - } - catch (json::parse_error& e) { - ShowError("Error parsing bot_accounts.json: %s\n", e.what()); - return false; - } - // Close file after done with it - bot_itemdb_aegisname_to_id_file.close(); - - //ShowStatus("Mapping aegisname to id...\n"); - - if (!json_itemdb_aegisname_to_id.contains(aegisname)) { - // Error: aegisname is not inside json file - ShowWarning("Aegisname \"%s\" was not found inside the itemdb. Skipped...", aegisname); - return 0; - } + item.inventory_id = -1; + item.char_id = 0; + item.item_id = 512; // Default apple + item.amount = 1; + item.equip = 0; // is Overriden by value - const uint32 item_id = json_itemdb_aegisname_to_id[aegisname]; - //ShowStatus("aegisname found for Item_id %d\n",item_id); + item.identify = 1; // Default indentified + item.refine = 0; + item.attribute = 0; - return item_id; -} + item.card0 = 0; + item.card1 = 0; + item.card2 = 0; + item.card3 = 0; -std::string convert_item_id_to_name(uint32 item_id) -{ - // Open the mapping JSON file - std::ifstream bot_itemdb_item_id_to_name_file("conf/bot_helper_jsons/itemdb_converter_id_to_name.json"); - if (!bot_itemdb_item_id_to_name_file.is_open()) { - ShowError("Failed to open itemdb_converter_id_to_name.json\n"); - return "UNKNOWN"; - } - - // Parse the JSON file - json json_itemdb_id_to_name; - - try { - //bot_itemdb_item_id_to_name_file >> json_itemdb_id_to_name; - json test_json = json::parse(bot_itemdb_item_id_to_name_file); - //ShowDebug("Test parse successful: %s\n", test_json.dump().c_str()); - } - catch (const json::parse_error& e) { - ShowError("JSON parse error: %s\n", e.what()); - } - catch (const std::exception& e) { - ShowError("General exception: %s\n", e.what()); - } - catch (...) { - ShowError("Unknown error occurred during JSON parsing.\n"); - } - // Close file after done with it + item.option_id0 = 0; + item.option_val0 = 0; + item.option_parm0 = 0; - bot_itemdb_item_id_to_name_file.close(); + item.option_id1 = 0; + item.option_val1 = 0; + item.option_parm1 = 0; - //ShowStatus("Mapping item_id to item_name...\n"); - std::string id_str = std::to_string(item_id); + item.option_id2 = 0; + item.option_val2 = 0; + item.option_parm2 = 0; - //ShowStatus("id=%s\n", id_str.c_str()); - if (!json_itemdb_id_to_name.contains(id_str)) { - // Error: item_id is not inside json file - ShowWarning("Item_id \"%d\" was not found inside the itemdb. Skipped...\n", item_id); - return "UNKNOWN"; - } + item.option_id3 = 0; + item.option_val3 = 0; + item.option_parm3 = 0; - const std::string item_name = json_itemdb_id_to_name[id_str]; - //ShowStatus("Entry found. Item Name: %s\n", item_name.c_str()); + item.option_id4 = 0; + item.option_val4 = 0; + item.option_parm4 = 0; - return item_name; - + item.expire_time = 0; + item.favorite = 0; + item.bound = 0; + item.unique_id = 0; + item.equip_switch = 0; + item.enchantgrade = 0; } -std::string convert_item_id_to_aegisname(uint32 item_id) { - static json json_itemdb_id_to_aegisname; - static bool id_to_aegisname_is_loaded = false; - - // Load the JSON file only once - if (!id_to_aegisname_is_loaded) { - std::ifstream bot_itemdb_item_id_to_aegisname_file("conf/bot_helper_jsons/itemdb_converter_id_to_aegisname.json"); - if (!bot_itemdb_item_id_to_aegisname_file.is_open()) { - ShowError("Failed to open itemdb_converter_id_to_aegisname.json\n"); - return "UNKNOWN"; - } +void generate_crafted_item_attributes(bot_inv_item& item, uint32 creator_char_id, int element, int cnt_star_crumbs) { + // Ensure valid input + if (element < 1 || element > 4) { + ShowError("Invalid element type for crafted item. Valid range is 1-4.\n"); + return; + } + if (cnt_star_crumbs < 0) { + ShowError("Invalid number of Star Crumbs. Must be non-negative.\n"); + return; + } - try { - bot_itemdb_item_id_to_aegisname_file >> json_itemdb_id_to_aegisname; - id_to_aegisname_is_loaded = true; - } - catch (json::parse_error& e) { - ShowError("Error parsing itemdb_converter_id_to_aegisname.json: %s\n", e.what()); - return "UNKNOWN"; - } + // Assign crafted item flag to card1 + item.card0 = 255; // Indicates inscribed equipment - bot_itemdb_item_id_to_aegisname_file.close(); - } + // Compute card2 using the formula + item.card1 = element + ((cnt_star_crumbs * 5) << 8); - // Lookup the item ID in the JSON data - std::string id_str = std::to_string(item_id); - if (!json_itemdb_id_to_aegisname.contains(id_str)) { - ShowWarning("Item_id \"%u\" was not found inside the itemdb. Skipped...", item_id); - return "UNKNOWN"; - } + // Split inscriber ID into card3 and card4 + item.card2 = creator_char_id & 65535; // Lower 16 bits of inscriber_id + item.card3 = creator_char_id >> 16; // Upper 16 bits of inscriber_id - return json_itemdb_id_to_aegisname[id_str]; + ShowDebug("Crafted item attributes generated:\n"); + ShowDebug(" - Element: %d\n", element); + ShowDebug(" - Star Crumbs: %d\n", cnt_star_crumbs); + ShowDebug(" - Inscriber ID: %u (card2: %u, card3: %u)\n", creator_char_id, item.card2, item.card3); } /* -* Tool Functions for item name parsing +* Tool Functions for item preparation */ void prepare_item_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character) { @@ -1041,5 +896,519 @@ void prepare_item_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann bundle.items.push_back(new_item); } } +} + +void prepare_equipment_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character) { + //Select for the used profile + std::string equipment_profile = character["char_equip_profile"]; + if (equipment_profile == "CUSTOM") { + if (!character.contains("equip")) { + throw std::runtime_error("equip data is missing for CUSTOM equip profile."); + } + const auto& equip_items = character["equip"]; + //Initialize the items bundle + bundle.items.clear(); + // Iterate over the items in the JSON + for (const auto& equip_item : equip_items.items()) { + std::string equip_pos_str = equip_item.key(); + const auto& equip_item_data = equip_item.value(); + std::string equip_item_name; + //Einbauen: Check für crafted item + //generate_crafted_item_attributes(item, char_id, int element, int cnt_star_crumbs + uint32 equip_item_id = convert_item_aegisname_to_id(equip_item_name.c_str()); + // Convert item name to item ID + uint32 equip_pos_id = convert_equipslot_id_to_number(equip_pos_str.c_str()); + if (equip_pos_id <= 0) { // Invalid Equip slot ID + ShowWarning("Equip slot ID %s is Invalid. Skipping...\n", equip_pos_str.c_str()); + continue; // Skip invalid item + } + const std::string& equip_slot = equip_item.key(); + bot_inv_item new_item{}; + set_bot_inv_item_defaults(new_item); + // Convert item aegisname to item ID + // Read item information + if (equip_item_data.contains("AEGISNAME")) { + std::string aegisname = equip_item_data["AEGISNAME"]; + new_item.item_id = convert_item_aegisname_to_id(aegisname.c_str()); + if (new_item.item_id <= 0) { + ShowWarning("Invalid AEGISNAME %s for slot %s. Skipping...\n", aegisname.c_str(), equip_slot.c_str()); + continue; + } + } + else { + ShowWarning("Missing AEGISNAME for slot %s. Skipping...\n", equip_slot.c_str()); + continue; + } + new_item.char_id = char_id; + new_item.equip = equip_pos_id; + + // Read refine level + if (equip_item_data.contains("REFINELVL")) { + new_item.refine = equip_item_data["REFINELVL"]; + } + else { + new_item.refine = 0; // Default refine level + } + // Read card fields + if (equip_item_data.contains("CARD0")) { + new_item.card0 = convert_item_aegisname_to_id(equip_item_data["CARD0"].get().c_str()); + } + if (equip_item_data.contains("CARD1")) { + new_item.card1 = convert_item_aegisname_to_id(equip_item_data["CARD1"].get().c_str()); + } + if (equip_item_data.contains("CARD2")) { + new_item.card2 = convert_item_aegisname_to_id(equip_item_data["CARD2"].get().c_str()); + } + if (equip_item_data.contains("CARD3")) { + new_item.card3 = convert_item_aegisname_to_id(equip_item_data["CARD3"].get().c_str()); + } + // Handle crafted items + if (equip_item_data.contains("CRAFTED")) { + const nlohmann::json& crafted_data = equip_item_data["CRAFTED"]; + new_item.card0 = 255; // Crafted equipment flag + + if (crafted_data.contains("CREATOR")) { + std::string creator = crafted_data["CREATOR"]; + //uint32 inscriber_id = (creator == "SELF") ? char_id : get_char_id_by_name(creator.c_str()); + uint32 inscriber_id = (creator == "SELF") ? char_id : 0; // If creator is not self by default creator is "Nameless" + new_item.card2 = inscriber_id & 65535; + new_item.card3 = inscriber_id >> 16; + } + + if (crafted_data.contains("STAR_CRUMBS") && crafted_data.contains("ELEMENT")) { + int star_crumbs = crafted_data["STAR_CRUMBS"]; + std::string element = crafted_data["ELEMENT"]; + int element_id = convert_element_id_to_number(element); + new_item.card1 = element_id + ((star_crumbs * 5) << 8); + } + } + bundle.items.push_back(new_item); + } + + } + else { + // Load item loadout 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/equip_profiles/" + job_str + "_equip_profile.json"; + nlohmann::json json_equip_profiles; + // Try to Open iten Profile + std::ifstream equip_profile_file(job_profile_file_path); + if (!equip_profile_file.is_open()) { + ShowWarning("Failed to open equip_profile_file %s. Trying to load DEFAULT instead.\n", job_profile_file_path.c_str()); + job_profile_file_path = "conf/bot_profiles/equip_profiles/DEFAULT_equip_profile.json"; + equip_profile_file.open(job_profile_file_path); + if (!equip_profile_file.is_open()) { + throw std::runtime_error("Failed to open both job and default profiles."); + } + } + // Parse Profile Data + try { + equip_profile_file >> json_equip_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing equip profile JSON: " + std::string(e.what())); + } + if (!json_equip_profiles.contains(equipment_profile)) { + // If equipment profile is not found, default to INIT + equipment_profile = "INIT"; + } + const auto& equip_profile = json_equip_profiles[equipment_profile]; + + //Initialize the item bundle + bundle.items.clear(); + // Iterate through each equipment slot + for (auto& equip_entry : equip_profile.items()) { + std::string equip_slot = equip_entry.key(); + uint32 equip_pos_id = convert_equipslot_id_to_number(equip_slot.c_str()); + + const auto& equip_item_data = equip_entry.value(); + bot_inv_item new_item{}; + set_bot_inv_item_defaults(new_item); + // Convert item aegisname to item ID + // Read item information + if (equip_item_data.contains("AEGISNAME")) { + std::string aegisname = equip_item_data["AEGISNAME"]; + new_item.item_id = convert_item_aegisname_to_id(aegisname.c_str()); + if (new_item.item_id <= 0) { + ShowWarning("Invalid AEGISNAME %s for slot %s. Skipping...\n", aegisname.c_str(), equip_slot.c_str()); + continue; + } + } + else { + ShowWarning("Missing AEGISNAME for slot %s. Skipping...\n", equip_slot.c_str()); + continue; + } + new_item.char_id = char_id; + new_item.equip = equip_pos_id; + + // Read refine level + if (equip_item_data.contains("REFINELVL")) { + new_item.refine = equip_item_data["REFINELVL"]; + } + else { + new_item.refine = 0; // Default refine level + } + // Read card fields + if (equip_item_data.contains("CARD0")) { + new_item.card0 = convert_item_aegisname_to_id(equip_item_data["CARD0"].get().c_str()); + } + if (equip_item_data.contains("CARD1")) { + new_item.card1 = convert_item_aegisname_to_id(equip_item_data["CARD1"].get().c_str()); + } + if (equip_item_data.contains("CARD2")) { + new_item.card2 = convert_item_aegisname_to_id(equip_item_data["CARD2"].get().c_str()); + } + if (equip_item_data.contains("CARD3")) { + new_item.card3 = convert_item_aegisname_to_id(equip_item_data["CARD3"].get().c_str()); + } + // Handle crafted items + if (equip_item_data.contains("CRAFTED")) { + const nlohmann::json& crafted_data = equip_item_data["CRAFTED"]; + new_item.card0 = 255; // Crafted equipment flag + + if (crafted_data.contains("CREATOR")) { + std::string creator = crafted_data["CREATOR"]; + //uint32 inscriber_id = (creator == "SELF") ? char_id : get_char_id_by_name(creator.c_str()); + uint32 inscriber_id = (creator == "SELF") ? char_id : 0; // If creator is not self by default creator is "Nameless" + new_item.card2 = inscriber_id & 65535; + new_item.card3 = inscriber_id >> 16; + } + + if (crafted_data.contains("STAR_CRUMBS") && crafted_data.contains("ELEMENT")) { + int star_crumbs = crafted_data["STAR_CRUMBS"]; + std::string element = crafted_data["ELEMENT"]; + int element_id = convert_element_id_to_number(element); + new_item.card1 = element_id + ((star_crumbs * 5) << 8); + } + } + bundle.items.push_back(new_item); + } + } +} + +/* +* Converter Functions for item parsing +*/ + +uint32 convert_item_aegisname_to_id(const char* aegisname) { + if (!itemdbs_loaded) { + load_itemdbs(); + } + + if (!cached_itemdb_aegisname_to_id.contains(aegisname)) { + ShowWarning("Aegisname \"%s\" was not found inside the itemdb. Skipped...\n", aegisname); + return 0; + } + + const uint32 item_id = cached_itemdb_aegisname_to_id[aegisname]; + //ShowStatus("aegisname found for Item_id %d\n",item_id); + + return item_id; +} + +void load_itemdb_aegisname_to_id() { + if (itemdbs_loaded) return; // Already loaded, skip + + std::ifstream bot_itemdb_aegisname_to_id_file("conf/bot_helper_jsons/itemdb_converter_aegisname_to_id.json"); + if (!bot_itemdb_aegisname_to_id_file.is_open()) { + ShowError("Failed to open itemdb_converter_aegisname_to_id.json\n"); + return; + } + + try { + bot_itemdb_aegisname_to_id_file >> cached_itemdb_aegisname_to_id; + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdb_converter_aegisname_to_id.json: %s\n", e.what()); + throw e; + } + + bot_itemdb_aegisname_to_id_file.close(); +} + +std::string convert_item_id_to_name(uint32 item_id) +{ + if (!itemdbs_loaded) { + load_itemdbs(); + } + //ShowStatus("Mapping item_id to item_name...\n"); + std::string id_str = std::to_string(item_id); + + if (!cached_itemdb_id_to_name.contains(id_str)) { + ShowWarning("id_str \"%s\" was not found inside the itemdb. Skipped...\n", id_str.c_str()); + return 0; + } + + std::string item_name = cached_itemdb_id_to_name[id_str]; + //ShowStatus("item id found %d\n",item_name.c_str()); + + return item_name; +} + +void load_itemdb_id_to_name() { + if (itemdbs_loaded) return; // Already loaded, skip + + std::ifstream bot_itemdb_id_to_name_file("conf/bot_helper_jsons/itemdb_converter_id_to_name.json"); + if (!bot_itemdb_id_to_name_file.is_open()) { + ShowError("Failed to open itemdb_converter_id_to_name.json\n"); + return; + } + + try { + bot_itemdb_id_to_name_file >> cached_itemdb_id_to_name; + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdb_converter_id_to_name.json: %s\n", e.what()); + throw e; + } + + bot_itemdb_id_to_name_file.close(); +} + +std::string convert_item_id_to_aegisname(uint32 item_id) { + if (!itemdbs_loaded) { + load_itemdbs(); + } + //ShowStatus("Mapping item_id to item_name...\n"); + std::string id_str = std::to_string(item_id); + + if (!cached_itemdb_id_to_aegisname.contains(id_str)) { + ShowWarning("id_str \"%s\" was not found inside the itemdb. Skipped...\n", id_str.c_str()); + return 0; + } + + std::string item_aegisname = cached_itemdb_id_to_aegisname[id_str]; + //ShowStatus("item id found %d\n",item_name.c_str()); + + return item_aegisname; +} + +void load_itemdb_id_to_aegisname() { + if (itemdbs_loaded) return; // Already loaded, skip + std::ifstream bot_itemdb_id_to_aegisname_file("conf/bot_helper_jsons/itemdb_converter_id_to_aegisname.json"); + if (!bot_itemdb_id_to_aegisname_file.is_open()) { + ShowError("Failed to open itemdb_converter_id_to_aegisname.json\n"); + return; + } + + try { + bot_itemdb_id_to_aegisname_file >> cached_itemdb_id_to_aegisname; + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdb_converter_id_to_aegisname.json: %s\n", e.what()); + throw e; + } + + bot_itemdb_id_to_aegisname_file.close(); +} + +uint32 convert_equipslot_id_to_number(const char* equipslot_id) +{ + static const std::unordered_map equipslot_map = { + {"EQP_HEAD_LOW" ,EQP_HEAD_LOW }, + {"EQP_HEAD_MID" ,EQP_HEAD_MID }, + {"EQP_HEAD_TOP" ,EQP_HEAD_TOP }, + + {"EQP_HEAD_LOWMID" ,EQP_HEAD_LOW + EQP_HEAD_MID }, + {"EQP_HEAD_LOWTOP" ,EQP_HEAD_LOW + EQP_HEAD_TOP }, + {"EQP_HEAD_MIDTOP" ,EQP_HEAD_MID + EQP_HEAD_TOP }, + {"EQP_HEAD_ALL" ,EQP_HEAD_LOW + EQP_HEAD_MID + EQP_HEAD_TOP }, + + {"EQP_HAND_R" ,EQP_HAND_R }, + {"EQP_HAND_L" ,EQP_HAND_L }, + {"EQP_HAND_BOTH" ,EQP_HAND_R + EQP_HAND_L }, + + {"EQP_ARMOR" ,EQP_ARMOR }, + {"EQP_SHOES" ,EQP_SHOES }, + {"EQP_GARMENT" ,EQP_GARMENT }, + {"EQP_ACC_R" ,EQP_ACC_R }, + {"EQP_ACC_L" ,EQP_ACC_L }, + + {"EQP_COSTUME_HEAD_TOP" ,EQP_COSTUME_HEAD_TOP }, + {"EQP_COSTUME_HEAD_MID" ,EQP_COSTUME_HEAD_MID }, + {"EQP_COSTUME_HEAD_LOW" ,EQP_COSTUME_HEAD_LOW }, + + {"EQP_COSTUME_HEAD_LOWMID" ,EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_MID }, + {"EQP_COSTUME_HEAD_LOWTOP" ,EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_TOP }, + {"EQP_COSTUME_HEAD_MIDTOP" ,EQP_COSTUME_HEAD_MID + EQP_COSTUME_HEAD_TOP }, + {"EQP_COSTUME_HEAD_ALL" ,EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_MID + EQP_COSTUME_HEAD_TOP }, + + {"EQP_COSTUME_GARMENT" ,EQP_COSTUME_GARMENT }, + {"EQP_AMMO" ,EQP_AMMO }, + {"EQP_SHADOW_ARMOR" ,EQP_SHADOW_ARMOR }, + {"EQP_SHADOW_WEAPON" ,EQP_SHADOW_WEAPON }, + {"EQP_SHADOW_SHIELD" ,EQP_SHADOW_SHIELD }, + {"EQP_SHADOW_SHOES" ,EQP_SHADOW_SHOES }, + {"EQP_SHADOW_ACC_R" ,EQP_SHADOW_ACC_R }, + {"EQP_SHADOW_ACC_L" ,EQP_SHADOW_ACC_L } + }; + // Look up the skill_id in the map + std::string equipslot = equipslot_id; + + auto it = equipslot_map.find(equipslot); + if (it != equipslot_map.end()) { + return it->second; // Return the corresponding str value + } + else { + ShowError("Unknown equipslot ID: %s\n", equipslot_id); + return 0; // Return an error value if the equipslot ID is unknown + } +} + +const char* convert_equipslot_id_to_str(uint32 equipslot_id) +{ + static const std::unordered_map equipslot_map = { + {EQP_HEAD_LOW ,"EQP_HEAD_LOW" }, + {EQP_HEAD_MID ,"EQP_HEAD_MID" }, + {EQP_HEAD_TOP ,"EQP_HEAD_TOP" }, + + {EQP_HEAD_LOW + EQP_HEAD_MID ,"EQP_HEAD_LOWMID" }, + {EQP_HEAD_LOW + EQP_HEAD_TOP ,"EQP_HEAD_LOWTOP" }, + {EQP_HEAD_MID + EQP_HEAD_TOP ,"EQP_HEAD_MIDTOP" }, + {EQP_HEAD_LOW + EQP_HEAD_MID + EQP_HEAD_TOP ,"EQP_HEAD_ALL" }, + + {EQP_HAND_R ,"EQP_HAND_R" }, + {EQP_HAND_L ,"EQP_HAND_L" }, + {EQP_HAND_R + EQP_HAND_L ,"EQP_HAND_BOTH" }, + + {EQP_ARMOR ,"EQP_ARMOR" }, + {EQP_SHOES ,"EQP_SHOES" }, + {EQP_GARMENT ,"EQP_GARMENT" }, + {EQP_ACC_R ,"EQP_ACC_R" }, + {EQP_ACC_L ,"EQP_ACC_L" }, + + {EQP_COSTUME_HEAD_TOP ,"EQP_COSTUME_HEAD_TOP" }, + {EQP_COSTUME_HEAD_MID ,"EQP_COSTUME_HEAD_MID" }, + {EQP_COSTUME_HEAD_LOW ,"EQP_COSTUME_HEAD_LOW" }, + + {EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_MID,"EQP_COSTUME_HEAD_LOWMID" }, + {EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_TOP,"EQP_COSTUME_HEAD_LOWTOP" }, + {EQP_COSTUME_HEAD_MID + EQP_COSTUME_HEAD_TOP,"EQP_COSTUME_HEAD_MIDTOP" }, + {EQP_COSTUME_HEAD_LOW + EQP_COSTUME_HEAD_MID + EQP_COSTUME_HEAD_TOP ,"EQP_COSTUME_HEAD_ALL" }, + + {EQP_COSTUME_GARMENT ,"EQP_COSTUME_GARMENT" }, + {EQP_AMMO ,"EQP_AMMO" }, + {EQP_SHADOW_ARMOR ,"EQP_SHADOW_ARMOR" }, + {EQP_SHADOW_WEAPON ,"EQP_SHADOW_WEAPON" }, + {EQP_SHADOW_SHIELD ,"EQP_SHADOW_SHIELD" }, + {EQP_SHADOW_SHOES ,"EQP_SHADOW_SHOES" }, + {EQP_SHADOW_ACC_R ,"EQP_SHADOW_ACC_R" }, + {EQP_SHADOW_ACC_L ,"EQP_SHADOW_ACC_L" } + }; + + + // Look up the equip_id in the map + uint32 equip_item = (uint32)equipslot_id; + + auto it = equipslot_map.find(equipslot_id); + if (it != equipslot_map.end()) { + return it->second.c_str(); // Return the corresponding str value + } + else { + ShowError("Unknown equipslot_id ID: %s\n", equipslot_id); + return "UNKNOWN"; // Return an error value if the equip ID is unknown + } +} + +int convert_element_id_to_number(const std::string& element_name) { + static const std::unordered_map element_map = { + {"ICE", 1}, + {"EARTH", 2}, + {"FIRE", 3}, + {"WIND", 4}, + }; + + auto it = element_map.find(element_name); + return (it != element_map.end()) ? it->second : 0; +} + +const char* convert_element_id_to_str(uint8 element_id) { + static const std::unordered_map element_map = { + { 1 , "ICE"}, + { 2 , "EARTH"}, + { 3 , "FIRE"}, + { 4 , "WIND"}, + }; + + auto it = element_map.find(element_id); + if (it != element_map.end()) { + return it->second.c_str(); // Return the corresponding str value + } + else { + ShowError("Unknown element_id ID: %s\n", element_id); + return "UNKNOWN"; // Return an error value if the element ID is unknown + } +} +// Function to convert job_id string to job number +int convert_weaponid_to_number(const char* weapon_id) { + // Create a map for the job ID strings to the corresponding e_job enum values + static const std::unordered_map weapon_map = { + {"W_FIST", W_FIST}, //Bare hands + {"W_DAGGER", W_DAGGER}, //1 + {"W_1HSWORD", W_1HSWORD}, //2 + {"W_2HSWORD", W_2HSWORD}, //3 + {"W_1HSPEAR", W_1HSPEAR}, //4 + {"W_2HSPEAR", W_2HSPEAR}, //5 + {"W_1HAXE", W_1HAXE}, //6 + {"W_2HAXE", W_2HAXE}, //7 + {"W_MACE", W_MACE}, //8 + {"W_2HMACE", W_2HMACE}, //9 (unused) + {"W_STAFF", W_STAFF}, //10 + {"W_BOW", W_BOW}, //11 + {"W_KNUCKLE", W_KNUCKLE}, //12 + {"W_MUSICAL", W_MUSICAL}, //13 + {"W_WHIP", W_WHIP}, //14 + {"W_BOOK", W_BOOK}, //15 + {"W_KATAR", W_KATAR}, //16 + {"W_REVOLVER", W_REVOLVER}, //17 + {"W_RIFLE", W_RIFLE}, //18 + {"W_GATLING", W_GATLING}, //19 + {"W_SHOTGUN", W_SHOTGUN}, //20 + {"W_GRENADE", W_GRENADE}, //21 + {"W_HUUMA", W_HUUMA}, //22 + {"W_2HSTAFF", W_2HSTAFF}, //23 + {"MAX_WEAPON_TYPE", MAX_WEAPON_TYPE}, + // dual-wield constants + {"W_DOUBLE_DD", W_DOUBLE_DD}, // 2 daggers + {"W_DOUBLE_SS", W_DOUBLE_SS}, // 2 swords + {"W_DOUBLE_AA", W_DOUBLE_AA}, // 2 axes + {"W_DOUBLE_DS", W_DOUBLE_DS}, // dagger + sword + {"W_DOUBLE_DA", W_DOUBLE_DA}, // dagger + axe + {"W_DOUBLE_SA", W_DOUBLE_SA}, // sword + axe + {"MAX_WEAPON_TYPE_ALL", MAX_WEAPON_TYPE_ALL}, + {"W_SHIELD", W_SHIELD}, + // Add other job mappings here as needed + }; + + // Look up the job_id in the map + std::string weapon_str = weapon_id; + // Convert to upper case + for (int index = 0; index < weapon_str.size(); index++) { + weapon_str.at(index) = toupper(weapon_str.at(index)); + } + auto it = weapon_map.find(weapon_str); + if (it != weapon_map.end()) { + return it->second; // Return the corresponding enum value + } + else { + ShowError("Unknown weapon ID: %s\n", weapon_id); + return -1; // Return an error value if the weapon ID is unknown + } +} + +void load_itemdbs() +{ + try { + load_itemdb_id_to_aegisname(); + load_itemdb_id_to_name(); + load_itemdb_aegisname_to_id(); + itemdbs_loaded = true; + ShowInfo("ItemDbs loaded successfully into cache...\n"); + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdbs: %s\n", e.what()); + itemdbs_loaded = false; + } } diff --git a/src/bot/Brokk/helper_inventory.hpp b/src/bot/Brokk/helper_inventory.hpp index 0ac93c6..259b2db 100644 --- a/src/bot/Brokk/helper_inventory.hpp +++ b/src/bot/Brokk/helper_inventory.hpp @@ -52,12 +52,19 @@ struct bot_inv_item { // EQP_SHADOW_SHOES = 0x080000, // 524288 // EQP_SHADOW_ACC_R = 0x100000, // 1048576 // EQP_SHADOW_ACC_L = 0x200000, // 2097152 + // NOTE: If Equip is on multiple slots their value is added together + // For example: 2 Hand Weapons have an equip value of 34 = Lefthand (32) + Righthand (2) int identify; // state of identification | 1 = identified - uint8 refine; // state of refinement + uint8 refine; // state of refinement //Note: Shows the level of refinement (current max is +19) uint8 attribute; // elemental attribute // card fields represent inserted cards or other modifiers to an item (like very strong and link to creator,etc) + // Side note: When related to a crafted item, the following rules aply: + // card0 = 255 to indicate a crafted item + // card1 = element + ((star_crumbs * 5) << 8) + // card2 = char_id of creator (lower 16 bit of uint32) for example "char_id & 65535" + // card3 = char_id of creator (upper 16 bit of uint32) for example "char_id >> 16" uint32 card0; // cardid of inserted card || special values for different modifiers possible uint32 card1; uint32 card2; @@ -180,9 +187,8 @@ struct InventoryDB { }; void set_bot_inv_item_defaults(bot_inv_item& item); -uint32 convert_item_aegisname_to_id(const char* aegisname); -std::string convert_item_id_to_name(uint32 item_id); -std::string convert_item_id_to_aegisname(uint32 item_id); +void set_bot_equip_item_defaults(bot_inv_item& item); +void generate_crafted_item_attributes(bot_inv_item& item, uint32 inscriber_id, int element, int star_crumbs); struct bot_item { uint32 item_id; @@ -196,5 +202,17 @@ struct bot_item_bundle { }; void prepare_item_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character); +void prepare_equipment_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character); void dump_item_bundle(const bot_item_bundle& item_bundle); + +uint32 convert_equipslot_id_to_number(const char* equipslot_id); +const char* convert_equipslot_id_to_str(const uint32 equipslot_id); +int convert_weaponid_to_number(const char* weapon_id); +int convert_element_id_to_number(const std::string& element_name); +const char* convert_element_id_to_str(uint8 element_id); +uint32 convert_item_aegisname_to_id(const char* aegisname); +std::string convert_item_id_to_name(uint32 item_id); +std::string convert_item_id_to_aegisname(uint32 item_id); +void load_itemdbs(); + #endif /* HELPER_INVENTORY_HPP */ diff --git a/src/bot/Brokk/helper_skills.cpp b/src/bot/Brokk/helper_skills.cpp index 3db51c7..04b78c1 100644 --- a/src/bot/Brokk/helper_skills.cpp +++ b/src/bot/Brokk/helper_skills.cpp @@ -872,7 +872,7 @@ void dump_skill_bundle(const bot_skill_bundle& skill_bundle) { ShowDebug("End of skill bundle dump.\n"); } -void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { +void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id, bool debug_mode) { ShowInfo("Validating skill bundle for job ID: %s...\n", job_id); // Retrieve the skill tree for the specified job ID @@ -883,6 +883,8 @@ void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { return; } + if (debug_mode) { ShowDebug("Polo1\n"); } + std::vector valid_skills; // Temporary vector to store valid skills for (const auto& skill : skill_bundle.skills) { @@ -892,16 +894,18 @@ void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { ShowWarning("Skill %s not found in the skill tree for job ID %s. Skipping.\n",convert_skill_id_to_str(skill.skill_id), job_id); continue; } - + if (debug_mode) { ShowDebug("Polo2\n"); } std::shared_ptr skill_entry = it->second; bool requirements_met = true; // Flag to track if requirements are met - + if (debug_mode) { ShowDebug("Polo3\n"); } // Check required skills for (const auto& required_skill : skill_entry->need) { uint16 required_skill_id = required_skill.first; uint16 required_skill_level = required_skill.second; bool found = false; + if (debug_mode) { ShowDebug("Polo4\n"); } + // Search for the required skill in the bundle for (const auto& bundled_skill : skill_bundle.skills) { if (bundled_skill.skill_id == required_skill_id && bundled_skill.level >= required_skill_level) { @@ -911,12 +915,12 @@ void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { } if (!found) { - //ShowDebug("Skill ID %s requires skill ID %d with minimum level %d, but it is not found in the bundle.\n", - // convert_skill_id_to_str(skill.skill_id), required_skill_id, required_skill_level); + if (debug_mode) { ShowDebug("Skill ID %s requires skill ID %d with minimum level %d, but it is not found in the bundle.\n", convert_skill_id_to_str(skill.skill_id), required_skill_id, required_skill_level); } requirements_met = false; break; } } + if (debug_mode) { ShowDebug("Polo5\n"); } if (requirements_met) { //ShowDebug("Skill ID %s validated successfully.\n", convert_skill_id_to_str(skill.skill_id)); @@ -926,6 +930,7 @@ void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { ShowWarning("Skill ID %s does not meet requirements and will be removed from the bundle.\n", convert_skill_id_to_str(skill.skill_id)); } } + if (debug_mode) { ShowDebug("Polo6\n"); } // Update the skill bundle with valid skills skill_bundle.skills = std::move(valid_skills); @@ -933,10 +938,12 @@ void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id) { //ShowStatus("Skill bundle validation completed for job ID: %s.\n", job_id); } -void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character) { +void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character, bool debug_mode) { //Select for the used profile std::string skill_profile = character["char_skills_profile"]; + //if (debug_mode) { ShowDebug("Marco\n"); } + if (skill_profile == "CUSTOM") { if (!character.contains("skills")) { throw std::runtime_error("skills data is missing for CUSTOM skills profile."); @@ -979,7 +986,9 @@ void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohma 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; - + + //if (debug_mode) { ShowDebug("Polo1\n"); } + // Try to Open skill Profile std::ifstream skill_profile_file(job_profile_file_path); if (!skill_profile_file.is_open()) { @@ -990,7 +999,7 @@ void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohma throw std::runtime_error("Failed to open both job and default profiles."); } } - + //if (debug_mode) { ShowDebug("Polo2\n"); } // Parse Profile Data try { skill_profile_file >> json_skill_profiles; @@ -999,17 +1008,17 @@ void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohma throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what())); } - + //if (debug_mode) { ShowDebug("Polo3\n"); } 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(); - + //if (debug_mode) { ShowDebug("Polo4\n"); } // Iterate over the skills in the JSON for (const auto& skill : profile.items()) { std::string skill_name = skill.key(); @@ -1033,8 +1042,10 @@ void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohma new_skill.level = static_cast(skill_level); new_skill.flag = 0; // Default: 0 for permanent bundle.skills.push_back(new_skill); + + //if (debug_mode) { ShowDebug("Skill %s added to bundle.\n", skill_name.c_str()); } } - //dump_skill_bundle(bundle); + if (debug_mode) { dump_skill_bundle(bundle);} } } @@ -1580,7 +1591,7 @@ const char* convert_skill_id_to_str(int skill_id) { {SL_SKE ,"SL_SKE" }, {SL_SKA ,"SL_SKA" }, - //Seams like quest skills for 3rd classes + //Seams like quest skills for 3rd classes (Trans-3rd Skills) {SM_SELFPROVOKE ,"SM_SELFPROVOKE" }, { NPC_EMOTION_ON ,"NPC_EMOTION_ON" }, { ST_PRESERVE ,"ST_PRESERVE" }, diff --git a/src/bot/Brokk/helper_skills.hpp b/src/bot/Brokk/helper_skills.hpp index 108a32d..1d2c17f 100644 --- a/src/bot/Brokk/helper_skills.hpp +++ b/src/bot/Brokk/helper_skills.hpp @@ -154,7 +154,7 @@ public: extern BotSkillTreeDatabase bot_skill_tree_db; void init_bot_skill_tree_db(); -void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id); -void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character); +void validate_skill_bundle(bot_skill_bundle& skill_bundle, const char* job_id, bool debug_mode=false); +void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character, bool debug_mode=false); #endif /* HELPER_SKILLS_HPP */