From 8b50abda6307a3c199c600416e80dbbf572f1957 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 16 Jan 2025 21:02:54 +0100 Subject: [PATCH] [WIP] add inventory parsing and adding to a char --- .../helper_inv_converter_id_to_aegisname.hpp | 21 + src/bot/Brokk/helper_inventory.cpp | 823 ++++++++++++++++++ src/bot/Brokk/helper_inventory.hpp | 199 +++++ 3 files changed, 1043 insertions(+) create mode 100644 src/bot/Brokk/helper_inv_converter_id_to_aegisname.hpp create mode 100644 src/bot/Brokk/helper_inventory.cpp create mode 100644 src/bot/Brokk/helper_inventory.hpp diff --git a/src/bot/Brokk/helper_inv_converter_id_to_aegisname.hpp b/src/bot/Brokk/helper_inv_converter_id_to_aegisname.hpp new file mode 100644 index 0000000..6d54214 --- /dev/null +++ b/src/bot/Brokk/helper_inv_converter_id_to_aegisname.hpp @@ -0,0 +1,21 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef HELPER_INV_CONVERTER_ID_TO_AEGISNAME_HPP +#define HELPER_INV_CONVERTER_ID_TO_AEGISNAME_HPP + +#include "json.hpp" + +#include +#include +#include +#include +#include +#include +#include +//#include + +const char* convert_item_id_to_name(int item_id); +#endif /* HELPER_INV_CONVERTER_ID_TO_AEGISNAME_HPP */ diff --git a/src/bot/Brokk/helper_inventory.cpp b/src/bot/Brokk/helper_inventory.cpp new file mode 100644 index 0000000..13d4bfd --- /dev/null +++ b/src/bot/Brokk/helper_inventory.cpp @@ -0,0 +1,823 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + + +#include "helper_inventory.hpp" + +#include //min / max +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "json.hpp" // + +#include "brokk.hpp" // brokk_config +#include "char_creation_helpers.hpp" + +using namespace rathena; + +using json = nlohmann::json; + +/// global defines + +/// internal structure +typedef struct InventoryDB_SQL { + InventoryDB vtable; // public interface + Sql* bot_inventory; // SQL handle bot inventory storage + std::string db_hostname = "127.0.0.1"; + uint16 db_port = 3306; + std::string db_username = "ragnarok"; + std::string db_password = "ragnarok"; + std::string db_database = "ragnarok"; + std::string codepage = ""; + // other settings + bool case_sensitive; + //table name + char inventory_db[32]; + +} InventoryDB_SQL; + +/// internal structure +typedef struct InventoryDBIterator_SQL { + InventoryDBIterator vtable; // public interface + InventoryDB_SQL* db; + int char_id; + int last_inv_item_id; +} InventoryDBIterator_SQL; + +/// internal functions +static bool bot_inv_item_db_sql_init(InventoryDB* self); +static void bot_inv_item_db_sql_destroy(InventoryDB* self); +static bool bot_inv_item_db_sql_get_property(InventoryDB* self, const char* key, char* buf, size_t buflen); +static bool bot_inv_item_db_sql_set_property(InventoryDB* self, const char* option, const char* value); +static bool bot_inv_item_db_sql_create(InventoryDB* self, struct bot_inv_item* item); +static bool bot_inv_item_db_sql_remove(InventoryDB* self, const uint32 char_id, const uint32 inventory_id); +//static bool bot_inv_item_db_sql_save(InventoryDB* self, const struct bot_inv_item* item); +static bool bot_inv_item_db_sql_load_num(InventoryDB* self, struct bot_inv_item* item); +static InventoryDBIterator* bot_inv_item_db_sql_iterator(InventoryDB* self, const uint32 char_id); +static void bot_inv_item_db_sql_iter_destroy(InventoryDBIterator* self); +static bool bot_inv_item_db_sql_iter_next(InventoryDBIterator* self, const uint32 char_id, struct bot_inv_item* item); +static bool bot_inv_item_db_fromsql(InventoryDB_SQL* db, struct bot_inv_item* item); +static bool bot_inv_item_db_tosql(InventoryDB_SQL* db, const struct bot_inv_item* item, bool is_new); + + +/// public constructor +InventoryDB* inventory_db_sql(void) { + InventoryDB_SQL* db = (InventoryDB_SQL*)aCalloc(1, sizeof(InventoryDB_SQL)); + new(db) InventoryDB_SQL(); + + // set up the vtable + db->vtable.init = &bot_inv_item_db_sql_init; + db->vtable.destroy = &bot_inv_item_db_sql_destroy; + db->vtable.get_property = &bot_inv_item_db_sql_get_property; + db->vtable.set_property = &bot_inv_item_db_sql_set_property; + //db->vtable.save = &bot_inv_item_db_sql_save; + db->vtable.create = &bot_inv_item_db_sql_create; + db->vtable.remove = &bot_inv_item_db_sql_remove; + db->vtable.load_num = &bot_inv_item_db_sql_load_num; + db->vtable.iterator = &bot_inv_item_db_sql_iterator; + + // initialize to default values + db->bot_inventory = nullptr; + + // other settings + db->case_sensitive = false; + safestrncpy(db->inventory_db, "inventory", sizeof(db->inventory_db)); + + return &db->vtable; +} + + +/* ------------------------------------------------------------------------- */ + + +/** + * Establish the database connection. + * @param self: pointer to db + */ +static bool bot_inv_item_db_sql_init(InventoryDB* self) { + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + Sql* sql_handle; + + db->bot_inventory = Sql_Malloc(); + sql_handle = db->bot_inventory; + + if (SQL_ERROR == Sql_Connect(sql_handle, db->db_username.c_str(), db->db_password.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str())) + { + ShowError("Couldn't connect with uname='%s',host='%s',port='%hu',database='%s'\n", + db->db_username.c_str(), db->db_hostname.c_str(), db->db_port, db->db_database.c_str()); + Sql_ShowDebug(sql_handle); + Sql_Free(db->bot_inventory); + db->bot_inventory = nullptr; + return false; + } + + if (!db->codepage.empty() && SQL_ERROR == Sql_SetEncoding(sql_handle, db->codepage.c_str())) + Sql_ShowDebug(sql_handle); + + return true; +} + +/** + * Destroy the database and close the connection to it. + * @param self: pointer to db + */ +static void bot_inv_item_db_sql_destroy(InventoryDB* self){ + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + + + Sql_Free(db->bot_inventory); + db->bot_inventory = nullptr; + + db->~InventoryDB_SQL(); + aFree(db); +} + +/** + * Get configuration information into buf. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param buf: value set of the keyword + * @param buflen: size of buffer to avoid out of bound + * @return true if successful, false if something has failed + */ +static bool bot_inv_item_db_sql_get_property(InventoryDB* self, const char* key, char* buf, size_t buflen) +{ + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_hostname.c_str()); + else + if( strcmpi(key, "port") == 0 ) + safesnprintf(buf, buflen, "%hu", db->db_port); + else + if( strcmpi(key, "id") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_username.c_str()); + else + if( strcmpi(key, "pw") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_password.c_str()); + else + if( strcmpi(key, "db") == 0 ) + safesnprintf(buf, buflen, "%s", db->db_database.c_str()); + else + if( strcmpi(key, "inventory_db") == 0 ) + safesnprintf(buf, buflen, "%s", db->inventory_db); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + safesnprintf(buf, buflen, "%s", db->codepage.c_str()); + else + if( strcmpi(key, "case_sensitive") == 0 ) + safesnprintf(buf, buflen, "%d", (db->case_sensitive ? 1 : 0)); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Read and set configuration. + * If the option is supported, adjust the internal state. + * @param self: pointer to db + * @param key: config keyword + * @param value: config value for keyword + * @return true if successful, false if something has failed + */ +static bool bot_inv_item_db_sql_set_property(InventoryDB* self, const char* key, const char* value) { + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + const char* signature; + + signature = "brokk_server_"; + if( strncmp(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "ip") == 0 ) + db->db_hostname = value; + else + if( strcmpi(key, "port") == 0 ) + db->db_port = (uint16)strtoul( value, nullptr, 10 ); + else + if( strcmpi(key, "id") == 0 ) + db->db_username = value; + else + if( strcmpi(key, "pw") == 0 ) + db->db_password = value; + else + if( strcmpi(key, "db") == 0 ) + db->db_database = value; + else + if( strcmpi(key, "inventory_db") == 0 ) + safestrncpy(db->inventory_db, value, sizeof(db->inventory_db)); + else + return false;// not found + return true; + } + + signature = "brokk_"; + if( strncmpi(key, signature, strlen(signature)) == 0 ) { + key += strlen(signature); + if( strcmpi(key, "codepage") == 0 ) + db->codepage = value; + else + if( strcmpi(key, "case_sensitive") == 0 ) + db->case_sensitive = (config_switch(value)==1); + else + return false;// not found + return true; + } + + return false;// not found +} + +/** + * Create a new inventory entry. + * bot_item->char_id & -> item_id are primary keys, + * and its values will be written to bot_inventpory + * @param self: pointer to db + * @param item: pointer of bot_inv_item to save + * @return true if successful, false if something has failed + */ +static bool bot_inv_item_db_sql_create(InventoryDB* self, struct bot_inv_item* item) { + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + Sql* sql_handle_bot = db->bot_inventory; + + // validate char id and skill_id to assign + uint32 char_id, item_id; + char_id = item->char_id; + item_id = item->item_id; + + // zero value is prohibited + if(char_id == 0 || item_id == 0 ) + return false; + + return bot_inv_item_db_tosql(db, item, true); +} + +/** + * Delete an existing bot inventory entry. + * @param self: pointer to db + * @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; +} + +/** + * Retrieve data from db and store it in the provided data structure. + * Filled data structure is done by delegation to bot_inv_item_db_fromsql. + * @param self: pointer to db + * @param bot_inv_item: pointer of bot_inv_item to fill + * @param char_id: id of character + * @param item_id: id of item + * @return true if successful, false if something has failed + */ +static bool bot_inv_item_db_sql_load_num(InventoryDB* self, struct bot_inv_item* item) { + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + return bot_inv_item_db_fromsql(db, item); +} + + +/** + * Create a new forward iterator. + * @param self: pointer to db iterator + * @return a new db iterator + */ +static InventoryDBIterator* bot_inv_item_db_sql_iterator(InventoryDB* self, const uint32 char_id) { + InventoryDB_SQL* db = (InventoryDB_SQL*)self; + InventoryDBIterator_SQL* iter = (InventoryDBIterator_SQL*)aCalloc(1, sizeof(InventoryDBIterator_SQL)); + + // set up the vtable + iter->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; +} + +/** + * Destroys this iterator, releasing all allocated memory (including itself). + * @param self: pointer to db iterator + */ +static void bot_inv_item_db_sql_iter_destroy(InventoryDBIterator* self) { + InventoryDBIterator_SQL* iter = (InventoryDBIterator_SQL*)self; + aFree(iter); +} + +/** + * Fetches the next inv item entry in the database. + * @param self: pointer to db iterator + * @param char_id: char_id of the bot the items belong to + * @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, const uint32 char_id, 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; + inventory_id = atoi(data); uint32 skill_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; +} + +/** + * 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 `char_id` = %d AND `id` = %d", +#else + "SELECT `id`, `char_id`, `nameid`, `amount`, `equip`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `enchantgrade` FROM `%s` WHERE `char_id` = %d AND `id` = %d", +#endif + db->inventory_db, item->char_id, 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; +} + +/** + * Save a struct bot_inv_item in sql. + * @param db: pointer to db + * @param bot_inv_item: pointer of bot_inv_item to save + * @üaram char_id: char_id of the character the item belongs to + * @param is_new: if it's a new entry or should we update + * @return true if successful, false if something has failed + */ +static bool bot_inv_item_db_tosql(InventoryDB_SQL* db, const struct bot_inv_item* item, bool is_new) { + Sql* sql_handle = db->bot_inventory; + SqlStmt* stmt = SqlStmt_Malloc(sql_handle); + bool result = false; + + // try + do + { + + if( SQL_SUCCESS != Sql_QueryStr(sql_handle, "START TRANSACTION") ) + { + Sql_ShowDebug(sql_handle); + break; + } + + if( is_new ) + {// insert into account table + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `enchantgrade`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +#else + "INSERT INTO `%s` (`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `enchantgrade`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", +#endif + db->inventory_db) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_UINT, (void*)&item->char_id, sizeof(item->char_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 1, SQLDT_UINT, (void*)&item->item_id, sizeof(item->item_id)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 2, SQLDT_UINT, (void*)&item->amount, sizeof(item->amount)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 3, SQLDT_UINT, (void*)&item->equip, sizeof(item->equip)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 4, SQLDT_INT, (void*)&item->identify, sizeof(item->identify)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 5, SQLDT_UINT8, (void*)&item->refine, sizeof(item->refine)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 6, SQLDT_UINT8, (void*)&item->attribute, sizeof(item->attribute)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 7, SQLDT_UINT, (void*)&item->card0, sizeof(item->card0)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 8, SQLDT_UINT, (void*)&item->card1, sizeof(item->card1)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 9, SQLDT_UINT, (void*)&item->card2, sizeof(item->card2)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_UINT, (void*)&item->card3, sizeof(item->card3)) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_UINT8, (void*)&item->enchantgrade, sizeof(item->enchantgrade)) + + +//ifdef VIP_ENABLE +//endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + else + { // update is bot state (meaning if controlled by sindri or by other means) + // effectively there should be no reason to every change a bots loadout after creation. + // Per default, amount of an allready existing item is updated here but currently (01/25) no valid usecase is defined yet + if( SQL_SUCCESS != SqlStmt_Prepare(stmt, +#ifdef VIP_ENABLE + "UPDATE `%s` SET `amount`=? WHERE `char_id` = '%d' AND `id` = '%d'", +#else + "UPDATE `%s` SET `amount`=? WHERE `char_id` = '%d' AND `id` = '%d'", +#endif + db->inventory_db, item->char_id, item->inventory_id) + || SQL_SUCCESS != SqlStmt_BindParam(stmt, 0, SQLDT_UINT32, (void*)item->amount, sizeof(item->amount)) +#ifdef VIP_ENABLE +#endif + || SQL_SUCCESS != SqlStmt_Execute(stmt) + ) { + SqlStmt_ShowDebug(stmt); + break; + } + } + + // if we got this far, everything was successful + result = true; + + } while (0); + // finally + + result &= (SQL_SUCCESS == Sql_QueryStr(sql_handle, (result == true) ? "COMMIT" : "ROLLBACK")); + SqlStmt_Free(stmt); + + return result; +} + +/** + * Sets default values for a bot_inv_item + * @param item: pointer to bot_inv_item + */ +void set_bot_inv_item_defaults(bot_inv_item& item) { + + item.inventory_id = -1; + item.char_id = 0; + item.item_id = 512; // Default apple + item.amount = 0; + item.equip = 0; + + item.identify = 1; // Default indentified + item.refine = 0; + item.attribute = 0; + + item.card0 = 0; + item.card1 = 0; + item.card2 = 0; + item.card3 = 0; + + item.option_id0 = 0; + item.option_val0 = 0; + item.option_parm0 = 0; + + item.option_id1 = 0; + item.option_val1 = 0; + item.option_parm1 = 0; + + item.option_id2 = 0; + item.option_val2 = 0; + item.option_parm2 = 0; + + item.option_id3 = 0; + item.option_val3 = 0; + item.option_parm3 = 0; + + item.option_id4 = 0; + item.option_val4 = 0; + item.option_parm4 = 0; + + item.expire_time = 0; + item.favorite = 0; + item.bound = 0; + item.unique_id = 0; + item.equip_switch = 0; + item.enchantgrade = 0; +} + +int convert_item_aegisname_to_id(const char* aegisname) +{ + // 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; + } + + const int item_id = json_itemdb_aegisname_to_id[aegisname]; + //ShowStatus("aegisname found for Item_id %d\n",item_id); + + return item_id; +} + +std::string convert_item_id_to_name(int 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; + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdb_converter_id_to_name.json: %s\n", e.what()); + return "UNKNOWN"; + } + // Close file after done with it + bot_itemdb_item_id_to_name_file.close(); + + //ShowStatus("Mapping item_id to item_name...\n"); + std::string id_str = std::to_string(item_id); + + //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...", item_id); + return "UNKNOWN"; + } + + const std::string item_name = json_itemdb_id_to_name[id_str]; + //ShowStatus("Entry found. Item Name: %s\n", item_name.c_str()); + + return item_name; + +} + +std::string convert_item_id_to_aegisname(int item_id) +{ + // Open the mapping JSON file + 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"; + } + + // Parse the JSON file + json json_itemdb_id_to_aegisname; + try { + bot_itemdb_item_id_to_aegisname_file >> json_itemdb_id_to_aegisname; + } + catch (json::parse_error& e) { + ShowError("Error parsing itemdb_converter_id_to_name.json: %s\n", e.what()); + return "UNKNOWN"; + } + // Close file after done with it + bot_itemdb_item_id_to_aegisname_file.close(); + + //ShowStatus("Mapping item_id to item aegisname...\n"); + std::string id_str = std::to_string(item_id); + + //ShowStatus("id=%s\n", id_str.c_str()); + if (!json_itemdb_id_to_aegisname.contains(id_str)) { + // Error: item_id is not inside json file + ShowWarning("Item_id \"%d\" was not found inside the itemdb. Skipped...", item_id); + return "UNKNOWN"; + } + + std::string item_name = json_itemdb_id_to_aegisname[id_str]; + //ShowStatus("Entry found. Item Name: %s\n", item_name.c_str()); + + return item_name; +} + +/* +* Tool Functions for item name parsing +*/ + +void prepare_item_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character) { + //Select for the used profile + std::string item_profile = character["char_items_profile"]; + + if (item_profile == "CUSTOM") { + if (!character.contains("items")) { + throw std::runtime_error("items data is missing for CUSTOM items profile."); + } + const auto& items = character["items"]; + //Initialize the items bundle + bundle.items.clear(); + + // Iterate over the items in the JSON + for (const auto& item : items.items()) { + std::string item_name = item.key(); + int item_amount = item.value(); + // Convert item name to item ID + int item_id = convert_item_aegisname_to_id(item_name.c_str()); + if (item_id <= 0) { // Invalid item ID + ShowWarning("Item ID (Aegisname) %s is Invalid. Skipping...\n", item_name.c_str()); + continue; // Skip invalid item + } + // Retrieve ingame name of the item + std::string ingame_name = convert_item_id_to_name(item_id); + if (ingame_name == "UNKNOWN") { // invalid ingame name + ShowWarning("Item id %d is Invalid. Skipping...\n", item_id); + continue; // Skip invalid item id + } + // Add the item to the item bundle + bot_inv_item new_item{}; + + set_bot_inv_item_defaults(new_item); + new_item.item_id = item_id; // the id number of the item + new_item.amount = item_amount; + new_item.char_id = char_id; + + 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/item_profiles/" + job_str + "_item_profile.json"; + nlohmann::json json_item_profiles; + // Try to Open iten Profile + std::ifstream item_profile_file(job_profile_file_path); + if (!item_profile_file.is_open()) { + ShowWarning("Failed to open item_profile_file %s. Trying to load DEFAULT instead.\n", job_profile_file_path.c_str()); + job_profile_file_path = "conf/bot_profiles/item_profiles/DEFAULT_item_profile.json"; + item_profile_file.open(job_profile_file_path); + if (!item_profile_file.is_open()) { + throw std::runtime_error("Failed to open both job and default profiles."); + } + } + // Parse Profile Data + try { + item_profile_file >> json_item_profiles; + } + catch (const nlohmann::json::parse_error& e) { + throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what())); + } + + if (!json_item_profiles.contains(item_profile)) { + // If build profile is not found, default to INIT + item_profile = "INIT"; + } + const auto& profile = json_item_profiles[item_profile]; + + //Initialize the item bundle + bundle.items.clear(); + + // Iterate over the item in the JSON + for (const auto& item : profile.items()) { + std::string item_name = item.key(); + int item_amount = item.value(); + int value = item.value(); + + // Convert item aegisname to item ID + int item_id = convert_item_aegisname_to_id(item_name.c_str()); + + if (item_id <= 0) { // Invalid item ID + ShowWarning("Item Aegisname %s is Invalid. Skipping...\n", item_name.c_str()); + continue; // Skip invalid items + } + + // Retrieve ingame name of the item + std::string ingame_name = convert_item_id_to_name(item_id); + + if (ingame_name == "UNKNOWN") { // invalid ingame name + ShowWarning("Item id %d is Invalid. Skipping...\n", item_id); + continue; // Skip invalid item id + } + // Add the item to the item bundle + bot_inv_item new_item{}; + + set_bot_inv_item_defaults(new_item); + + new_item.item_id = item_id; // the id number of the item + new_item.amount = item_amount; + new_item.char_id = char_id; + + bundle.items.push_back(new_item); + } + } + +} diff --git a/src/bot/Brokk/helper_inventory.hpp b/src/bot/Brokk/helper_inventory.hpp new file mode 100644 index 0000000..dba6aff --- /dev/null +++ b/src/bot/Brokk/helper_inventory.hpp @@ -0,0 +1,199 @@ +// Copyright (c) 4lexKidd +// This software is licensed under the GPL v3 as to be fitting with the rathena project with the additional requirement +// that any distributed versions must include an attribution to the original author (4lexKidd) in all copies +// or substantial portions of the software. + +#ifndef HELPER_INVENTORY_HPP +#define HELPER_INVENTORY_HPP + +#include "json.hpp" + +#include +#include +#include +#include +#include +#include +#include +//#include + +typedef struct InventoryDB InventoryDB; +typedef struct InventoryDBIterator InventoryDBIterator; + +// standard engines +InventoryDB* inventory_db_sql(void); + +struct bot_inv_item { + //base information + uint32 inventory_id; // ID of inventory entry + uint32 char_id; // ID of corresponding char account + uint32 item_id; // nameID of item + uint32 amount; // amount of item added + uint32 equip; // binary code for where item is equipped to + // EQP_HEAD_LOW = 0x000001, // 1 + // EQP_HEAD_MID = 0x000200, // 512 + // EQP_HEAD_TOP = 0x000100, // 256 + // EQP_HAND_R = 0x000002, // 2 + // EQP_HAND_L = 0x000020, // 32 + // EQP_ARMOR = 0x000010, // 16 + // EQP_SHOES = 0x000040, // 64 + // EQP_GARMENT = 0x000004, // 4 + // EQP_ACC_R = 0x000008, // 8 + // EQP_ACC_L = 0x000080, // 128 + // EQP_COSTUME_HEAD_TOP = 0x000400, // 1024 + // EQP_COSTUME_HEAD_MID = 0x000800, // 2048 + // EQP_COSTUME_HEAD_LOW = 0x001000, // 4096 + // EQP_COSTUME_GARMENT = 0x002000, // 8192 + // //EQP_COSTUME_FLOOR = 0x004000, // 16384 + // EQP_AMMO = 0x008000, // 32768 + // EQP_SHADOW_ARMOR = 0x010000, // 65536 + // EQP_SHADOW_WEAPON = 0x020000, // 131072 + // EQP_SHADOW_SHIELD = 0x040000, // 262144 + // EQP_SHADOW_SHOES = 0x080000, // 524288 + // EQP_SHADOW_ACC_R = 0x100000, // 1048576 + // EQP_SHADOW_ACC_L = 0x200000, // 2097152 + + int identify; // state of identification | 1 = identified + uint8 refine; // state of refinement + uint8 attribute; // elemental attribute + + // card fields represent inserted cards or other modifiers to an item (like very strong and link to creator,etc) + uint32 card0; // cardid of inserted card || special values for different modifiers possible + uint32 card1; + uint32 card2; + uint32 card3; + + //option fields - use ingame currently unknown + int16 option_id0; + int16 option_val0; + int8 option_parm0; + + int16 option_id1; + int16 option_val1; + int8 option_parm1; + + int16 option_id2; + int16 option_val2; + int8 option_parm2; + + int16 option_id3; + int16 option_val3; + int8 option_parm3; + + int16 option_id4; + int16 option_val4; + int8 option_parm4; + + //mics fields - if unknown leave at 0 + uint32 expire_time; + uint8 favorite; + uint8 bound; + uint64 unique_id; + uint32 equip_switch; + uint8 enchantgrade; +}; + + +struct InventoryDBIterator { + /// Destroys this iterator, releasing all allocated memory (including itself). + /// + /// @param self Iterator + void (*destroy)(InventoryDBIterator* self); + + /// Fetches the next item in the database. + /// Fills bot_inv_item with the item data. + /// @param self Iterator + /// @param item InvItem data + /// @return true if successful + bool (*next)(InventoryDBIterator* self, const uint32 char_id, struct bot_inv_item* item); +}; + + +struct InventoryDB { + /// Initializes this database, making it ready for use. + /// Call this after setting the properties. + /// + /// @param self Database + /// @return true if successful + bool (*init)(InventoryDB* self); + + /// Destroys this database, releasing all allocated memory (including itself). + /// + /// @param self Database + void (*destroy)(InventoryDB* self); + + /// Gets a property from this database. + /// These read-only properties must be implemented: + /// + /// @param self Database + /// @param key Property name + /// @param buf Buffer for the value + /// @param buflen Buffer length + /// @return true if successful + bool (*get_property)(InventoryDB* self, const char* key, char* buf, size_t buflen); + + /// Sets a property in this database. + /// + /// @param self Database + /// @param key Property name + /// @param value Property value + /// @return true if successful + bool (*set_property)(InventoryDB* self, const char* key, const char* value); + + /// Creates a new item in this database. + /// bot_inv_item->char_id must always be set, because there will never be a valid bot_inv_item without char-table account + /// + /// @param self Database + /// @param item bot_inv_item data + /// @return true if successful + bool (*create)(InventoryDB* self, struct bot_inv_item* item); + + /// Removes an inventory entry from this database. + /// + /// @param self Database + /// @param char_id character id + /// @param item_id id of iteml + /// @return true if successful + bool (*remove)(InventoryDB* self, const uint32 char_id, const uint32 inventory_id); + + + /// Modifies the data of an existing inventory entry. + /// Uses bot_inv_item->char_id and bot_inv_item->item_id to identify the entry. + /// + /// @param self Database + /// @param bot_inv_item data + /// @return true if successful + bool (*save)(InventoryDB* self, const struct bot_inv_item* item); + + /// Finds an omvemtory entry with char_id and item_id and copies it to bot_inv_item. + /// + /// @param self Database + /// @param bot_inv_item Pointer that receives the item data + /// @return true if successful + bool (*load_num)(InventoryDB* self, struct bot_inv_item* item); + + /// Returns a new forward iterator. + /// + /// @param self Database + /// @return Iterator + InventoryDBIterator* (*iterator)(InventoryDB* self, const uint32 char_id); +}; + +void set_bot_inv_item_defaults(bot_inv_item& item); +int convert_item_aegisname_to_id(const char* aegisname); +std::string convert_item_id_to_name(int item_id); +std::string convert_item_id_to_aegisname(int item_id); + +struct bot_item { + uint32 item_id; + std::string aegisname; + std::string name; + uint32 amount; +}; + +struct bot_item_bundle { + std::vector items; +}; + +void prepare_item_bundle(bot_item_bundle& bundle, uint32 char_id, const nlohmann::json& character); +#endif /* HELPER_INVENTORY_HPP */