Refactor bot_char creation + capsulating char- & skill helper fns

main
Alex 4 weeks ago
parent 064e5c31a1
commit 73fe6e48f6

@ -1533,7 +1533,7 @@ Body:
Novice: true Novice: true
Swordman: true Swordman: true
Crusader: true Crusader: true
- Job: Supernovice - Job: Super_novice
Inherit: Inherit:
Novice: true Novice: true
Tree: Tree:
@ -5574,7 +5574,7 @@ Body:
- Job: Super_Novice_E - Job: Super_Novice_E
Inherit: Inherit:
Novice: true Novice: true
Supernovice: true Super_novice: true
Tree: Tree:
- Name: PR_IMPOSITIO - Name: PR_IMPOSITIO
MaxLevel: 5 MaxLevel: 5
@ -7956,7 +7956,7 @@ Body:
- Job: Hyper_Novice - Job: Hyper_Novice
Inherit: Inherit:
Novice: true Novice: true
Supernovice: true Super_novice: true
Super_Novice_E: true Super_Novice_E: true
Tree: Tree:
- Name: HN_SELFSTUDY_TATICS - Name: HN_SELFSTUDY_TATICS

@ -28,6 +28,7 @@
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <vector>
#include <char/char.hpp> #include <char/char.hpp>
#include "pc.hpp" #include "pc.hpp"
@ -37,6 +38,7 @@
#include "json.hpp" // The header-only library for JSON parsing #include "json.hpp" // The header-only library for JSON parsing
//helpers (Store adepted functions from different areas in rathena //helpers (Store adepted functions from different areas in rathena
//#include "config_helpers.hpp" //#include "config_helpers.hpp"
#include "char_creation_helpers.hpp" #include "char_creation_helpers.hpp"
#include "helper_skills.hpp" #include "helper_skills.hpp"
@ -56,7 +58,7 @@ SkillDB* skills = nullptr;
// YAML DB Objects of all touched infrastucture // YAML DB Objects of all touched infrastucture
BotStatPointDatabase bot_statpoint_db; BotStatPointDatabase bot_statpoint_db;
BotJobDatabase bot_job_db; BotJobDatabase bot_job_db;
//SkillTreeDatabase skill_tree_db; //BotSkillTreeDatabase bot_skill_tree_db;
// Advanced subnet check [LuzZza] // Advanced subnet check [LuzZza]
struct s_subnet { struct s_subnet {
@ -77,7 +79,10 @@ struct online_login_data* login_get_online_user(uint32 account_id) {
*/ */
TIMER_FUNC(periodic_cleanup_timer) { TIMER_FUNC(periodic_cleanup_timer) {
bot_account_cleanup(); // Call the cleanup function // Warning!!!: This will auto delete all bots after a certain time, even in the middle of sessions.
// Bots will not be logged out upon deletion.
//bot_account_cleanup(); // Call the cleanup function only if configured
return 0; // Return 0 to keep the timer going return 0; // Return 0 to keep the timer going
} }
@ -101,7 +106,7 @@ void setup_periodic_cleanup_timer() {
* 2: Error while writing into bot_accounts table * 2: Error while writing into bot_accounts table
* 3: Registration denied * 3: Registration denied
*/ */
int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip, const bool auto_generated, int creator_id = 0, const char* creator_name = "Brokk") { int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const bool auto_generated, int creator_id = 0, const char* creator_name = "Brokk") {
static int num_regs = 0; // bot registration counter static int num_regs = 0; // bot registration counter
static t_tick new_reg_tick = 0; static t_tick new_reg_tick = 0;
t_tick tick = gettick(); t_tick tick = gettick();
@ -141,6 +146,7 @@ int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, con
safestrncpy(acc.email, "bot@brokk.ai", sizeof(acc.email)); // Default email for bots safestrncpy(acc.email, "bot@brokk.ai", sizeof(acc.email)); // Default email for bots
acc.expiration_time = 0; // Bots don't expire acc.expiration_time = 0; // Bots don't expire
safestrncpy(acc.lastlogin, "", sizeof(acc.lastlogin)); safestrncpy(acc.lastlogin, "", sizeof(acc.lastlogin));
const char* last_ip = "127.0.0.1"; //Allways localhost because bots will never be created elsewhere
safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip)); safestrncpy(acc.last_ip, last_ip, sizeof(acc.last_ip));
safestrncpy(acc.birthdate, "", sizeof(acc.birthdate)); safestrncpy(acc.birthdate, "", sizeof(acc.birthdate));
safestrncpy(acc.pincode, "", sizeof(acc.pincode)); safestrncpy(acc.pincode, "", sizeof(acc.pincode));
@ -241,7 +247,9 @@ int brokk_mmo_auth_delete_num(uint32 account_id) {
struct mmo_account acc; struct mmo_account acc;
struct mmo_bot_account bot_acc; struct mmo_bot_account bot_acc;
struct mmo_charakter charakter; struct mmo_charakter charakter;
struct bot_skill skill;
std::vector<uint32> char_ids_to_delete; std::vector<uint32> char_ids_to_delete;
std::vector<bot_skill> skills_to_delete;
// Check if the account exists // Check if the account exists
if (!login_accounts->load_num(login_accounts, &acc, account_id)) { if (!login_accounts->load_num(login_accounts, &acc, account_id)) {
@ -257,17 +265,18 @@ int brokk_mmo_auth_delete_num(uint32 account_id) {
return 1; // Bot account does not exist return 1; // Bot account does not exist
} }
// Delete login account and corresponding bot account // Step 1: Account Data (login table)
if (!login_accounts->remove(login_accounts, acc.account_id)) { if (!login_accounts->remove(login_accounts, acc.account_id)) {
ShowError("Failed to delete login account (account_id: %d)\n", account_id); ShowError("Failed to delete login account (account_id: %d)\n", account_id);
return 1; // Deletion failed return 1; // Deletion failed
} }
// Step 2: Bot-Account Data (bot_accounts table)
if (!bot_accounts->remove(bot_accounts, bot_acc.account_id)) { if (!bot_accounts->remove(bot_accounts, bot_acc.account_id)) {
ShowError("Failed to delete bot account (account_id: %d)\n", account_id); ShowError("Failed to delete bot account (account_id: %d)\n", account_id);
return 1; // Deletion failed return 1; // Deletion failed
} }
//Attempt to delete charakters of the bot (if any) // Step 3: Character Data (char table)
// First pass: Collect char_ids to delete based on config // First pass: Collect char_ids to delete based on config
CharakterDBIterator* iter_chars = charakters->iterator(charakters); // Create the iterator CharakterDBIterator* iter_chars = charakters->iterator(charakters); // Create the iterator
@ -281,13 +290,38 @@ int brokk_mmo_auth_delete_num(uint32 account_id) {
} }
iter_chars->destroy(iter_chars); // Destroy the iterator after use iter_chars->destroy(iter_chars); // Destroy the iterator after use
ShowDebug("DelMarco\n");
// Second pass: Delete the collected charakters // Second pass: Delete the collected charakters
for (const auto& char_id : char_ids_to_delete) { for (const auto& char_id : char_ids_to_delete) {
ShowDebug("DelPolo1\n");
ShowInfo("Deleting charakter with ID %d.\n", char_id); ShowInfo("Deleting charakter with ID %d.\n", char_id);
if (!charakters->remove(charakters, char_id)) { if (!charakters->remove(charakters, char_id)) {
ShowError("Failed to delete charakter (char_id: %s)\n", char_id); ShowError("Failed to delete charakter (char_id: %s)\n", char_id);
continue; // Deletion failed continue; // Deletion failed
} }
ShowDebug("DelPolo2\n");
// Step 3B: Attempt to delete skills of the char (if any)
// First pass: Collect skill_ids to delete based on config
SkillDBIterator* iter_skills = skills->iterator(skills, char_id); // Create the iterator
while (iter_skills->next(iter_skills, char_id, &skill)) {
std::string id_str = convert_skill_id_to_str(skill.skill_id);
//ShowDebug("Skill %s found. Schedule for deletion...\n", id_str.c_str());
skills_to_delete.push_back(skill);
}
iter_skills->destroy(iter_skills); // Destroy the iterator after use
ShowDebug("DelPolo3\n");
// Second pass: Delete the collected skills
for (const auto& skill_entry : skills_to_delete) {
//ShowInfo("Deleting skill with ID %d.\n", char_id);
if (!skills->remove(skills, char_id, skill_entry.skill_id)) {
ShowError("Failed to delete skill (skill_id: %s)\n", skill_entry.skill_id);
continue; // Deletion failed
}
else {
ShowNotice("Deleting of skill %s from char_id %d was successful\n", convert_skill_id_to_str(skill_entry.skill_id), char_id);
}
}
ShowDebug("DelPolo4\n");
} }
ShowNotice("Bot account deletion completed (account: %s, id: %d)\n\n", acc.userid, acc.account_id); ShowNotice("Bot account deletion completed (account: %s, id: %d)\n\n", acc.userid, acc.account_id);
@ -449,6 +483,8 @@ void brokk_set_defaults() {
void bot_account_cleanup() { void bot_account_cleanup() {
ShowStatus("Start bot deletion according to settings...\n");
struct mmo_bot_account bot_acc; struct mmo_bot_account bot_acc;
BotAccountDBIterator* iter_bots = bot_accounts->iterator(bot_accounts); BotAccountDBIterator* iter_bots = bot_accounts->iterator(bot_accounts);
std::vector<uint32> account_ids_to_delete; std::vector<uint32> account_ids_to_delete;
@ -467,11 +503,12 @@ void bot_account_cleanup() {
// Second pass: Delete the collected accounts // Second pass: Delete the collected accounts
for (const auto& account_id : account_ids_to_delete) { for (const auto& account_id : account_ids_to_delete) {
ShowInfo("Deleting bot account with ID %d.\n", account_id); //ShowInfo("Deleting bot account with ID %d.\n", account_id);
brokk_mmo_auth_delete_num(account_id); // Delete from `login`, `bot_accounts` and 'char' tables brokk_mmo_auth_delete_num(account_id); // Delete from `login`, `bot_accounts` and 'char' tables
} }
ShowNotice("Bot account destruction completed successfully.\n\n"); ShowStatus("Bot account destruction completed successfully.\n\n");
} }
/* /*
* initializes dbs for all used non-yaml databases * initializes dbs for all used non-yaml databases
@ -547,175 +584,114 @@ void set_last_login(char last_login[24]) {
} }
/** /**
* Create a new charakter and save it in db/sql. * Create a new character and save it in the database.
* @param account_id: int for account, the charakter belongs to * @param charakter: Pre-configured mmo_charakter object with all necessary fields populated.
* @param char_name: string for charakter name
* @param slot: int which charakter slot to put it in (char_num in db)
* @param <multiple stat fields>: stats of the charakter (str,agi,vit,etc)
* @param hair_style: int for hair_style id
* @param hair_color: int for hair_color id *
* @param start_job: short for job class
* @param sex: string for gender of character
* @return : * @return :
* -1: success * 0: error
* 0: unregistered id (invalid sex or failure to create in db) * >0: new char id
* 1: incorrect char_name (char_name too short or already exists)
* 2: Error while writing into char table
* 3: Registration denied (limit reached)
*/ */
int brokk_create_character(uint32 account_id, const char* char_name, int slot, int base_level, int job_level, int str, int agi, int vit, int int_, int dex, int luk, int hair_style, int hair_color, short start_job, char sex, const bool auto_generated) { bool brokk_create_character(mmo_charakter& charakter, int& char_id, const bool auto_generated) {
static int num_regs = 0; // char registration counter static int num_regs = 0; // Character registration counter
static t_tick new_reg_tick = 0; static t_tick new_reg_tick = 0;
t_tick tick = gettick(); t_tick tick = gettick();
struct mmo_charakter charakter;
struct mmo_account acc;
// Check if the account exists // Check if the account exists
if (!login_accounts->load_num(login_accounts, &acc, account_id)) { mmo_account acc;
ShowNotice("Attempt to create char for non-existent account (account_id: %s)\n", account_id); if (!login_accounts->load_num(login_accounts, &acc, charakter.account_id)) {
return 0; // Account does not exist ShowNotice("Attempt to create character for non-existent account (account_id: %u)\n", charakter.account_id);
return false; // Account does not exist
} }
// char registration flood protection // Registration flood protection
if (new_reg_tick == 0) if (!auto_generated) {
new_reg_tick = gettick(); if (new_reg_tick == 0)
if (auto_generated == false) { new_reg_tick = gettick();
if (DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= brokk_config.allowed_bot_creations) { if (DIFF_TICK(tick, new_reg_tick) < 0 && num_regs >= brokk_config.allowed_bot_creations) {
ShowNotice("Charakter registration denied (registration limit exceeded)\n"); ShowNotice("Character registration denied (registration limit exceeded)\n");
return 3; return false;
} }
} }
// Ensure valid sex input (M/F for bots) // Validate character's sex input (must be 'M' or 'F')
if (sex != 'M' && sex != 'F') if (charakter.sex != 'M' && charakter.sex != 'F') {
return 0; // Unregistered ID ShowNotice("Invalid character sex (char_name: %s, sex: %c)\n", charakter.name, charakter.sex);
return false; // Unregistered ID
// Check if the char already exists }
if (charakters->load_str(charakters, &charakter, char_name)) {
ShowNotice("Attempt to create an already existing charakter (CharName: %s, sex: %c)\n", char_name, sex); // Check if the character already exists
return 1; // Char exists mmo_charakter existing_char;
} if (charakters->load_str(charakters, &existing_char, charakter.name)) {
ShowNotice("Attempt to create an already existing character (char_name: %s, sex: %c)\n", charakter.name, charakter.sex);
//TODO Herausfinden welche char Level und stats wann plausibel sind. char_id = 1;
// -> Plausibility checks aus map.cpp extrahieren return false; // Character exists
//TODO2 Homogene Base-Level grenzen herausfinden (bei welchem Base-Level ist man welches Job-Level bei Renewal }
// Create charakter std::string char_dump = dump_mmo_charakter(charakter);
// Clear and populate charakter structure //ShowDebug(char_dump.c_str());
memset(&charakter, '\0', sizeof(charakter));
charakter.char_id = -1; // Assigned by char db
// Assign values from parameters
charakter.account_id = account_id;
charakter.char_num = slot;
safestrncpy(charakter.name, char_name, sizeof(charakter.name));
charakter.str = str;
charakter.agi = agi;
charakter.vit = vit;
charakter.int_ = int_;
charakter.dex = dex;
charakter.luk = luk;
charakter.hair = hair_style;
charakter.hair_color = hair_color;
charakter.class_ = start_job;
charakter.sex = sex;
// Default values for new character creation
charakter.base_level = base_level;
charakter.job_level = job_level;
charakter.base_exp = bot_job_db.get_baseExp(start_job, base_level);
charakter.job_exp = bot_job_db.get_jobExp(start_job, job_level);
charakter.zeny = 1000000;
charakter.pow = 0;
charakter.sta = 0;
charakter.wis = 0;
charakter.spl = 0;
charakter.con = 0;
charakter.crt = 0;
charakter.max_hp = 40 * (100 + vit) / 100; // Default calculation for max HP
charakter.hp = charakter.max_hp;
charakter.max_sp = 11 * (100 + int_) / 100; // Default calculation for max SP
charakter.sp = charakter.max_sp;
charakter.max_ap = 0; // Default AP for new characters
charakter.ap = 0;
// Calculate Status Points from current level
charakter.status_point = bot_statpoint_db.get_table_point(base_level);
charakter.skill_point = 0;
charakter.trait_point = bot_statpoint_db.get_trait_table_point(base_level);;
charakter.option = 0;
charakter.karma = 0;
charakter.manner = 0;
charakter.party_id = 0;
charakter.guild_id = 0;
charakter.pet_id = 0;
charakter.homun_id = 0;
charakter.elemental_id = 0;
charakter.body = 0;
charakter.weapon = 0;
charakter.shield = 0;
charakter.head_top = 0;
charakter.head_mid = 0;
charakter.head_bottom = 0;
charakter.robe = 0;
safestrncpy(charakter.last_map, "morocc", sizeof(charakter.last_map)); // Default spawn map
charakter.last_x = 159; // Default spawn position x
charakter.last_y = 91; // Default spawn position y
charakter.last_instanceid = 0;
safestrncpy(charakter.save_map, "morocc", sizeof(charakter.save_map)); // Default save point map
charakter.save_x = 156; // Default save point position x
charakter.save_y = 45; // Default save point position y
/*TODO Meta Profiles hier laden : Für payon_citizen
* safestrncpy(charakter.save_map, "payon", sizeof(charakter.save_map)); // Default save point map
* charakter.save_x = 161; // Default save point position x
* charakter.save_y = 57; // Default save point position y
*/
charakter.partner_id = 0;
charakter.online = 0;
charakter.fame = 0;
charakter.rename = 0;
charakter.delete_date = 0;
charakter.moves = 0;
charakter.unban_time = 0;
charakter.font = 0;
charakter.uniqueitem_counter = 0;
charakter.hotkey_rowshift = 0;
charakter.hotkey_rowshift2 = 0;
charakter.clan_id = 0;
charakter.title_id = 0;
charakter.show_equip = 0;
charakter.inventory_slots = 100; // Default inventory slots
charakter.body_direction = 0;
charakter.disable_call = 0;
// Set default timestamp for last login to current time (or keep it empty for new character)
memset(charakter.last_login, '\0', sizeof(charakter.last_login)); // Clear the memory
set_last_login(charakter.last_login);
//#ifdef VIP_ENABLE
//#endif
// Create the account in the database
if (!charakters->create(charakters, &charakter))
return 0; // Charakter creation failed
ShowNotice("Bot charakter creation successful (name: %s, char_id: %d, sex: %c)\n", charakter.name, charakter.char_id, charakter.sex);
// Create the character in the database
if (!charakters->create(charakters, &charakter)) {
ShowError("Failed to create character (char_name: %s)\n", charakter.name);
return false; // Character creation failed
}
// Log success
ShowNotice("Character creation successful (name: %s, char_id: %d, sex: %c)\n", charakter.name, charakter.char_id, charakter.sex);
// Registration rate limit logic // Update registration limit
if (DIFF_TICK(tick, new_reg_tick) > 0) { if (DIFF_TICK(tick, new_reg_tick) > 0) {
num_regs = 0; num_regs = 0;
new_reg_tick = tick + brokk_config.time_allowed * 1000; new_reg_tick = tick + brokk_config.time_allowed * 1000;
} }
++num_regs; ++num_regs;
//ShowInfo("Bot character created: %s (account: %s, char_name: %d)\n", charakter.account_id, charakter.name); Sleep(10);
return -1; if (!charakters->load_str(charakters, &existing_char, charakter.name)) {
ShowError("Attempt to load newly created character failed (char_name: %s, sex: %c)\n", charakter.name, charakter.sex);
return false; // Character does not exists
}
char_id = existing_char.char_id;
return true; // Success -> return new char_id
}
/**
* Add a skill to a charakters bundle in the database.
* @param skill: Pre-configured bot_skill object with all necessary fields populated.
* @return :
* -1: success
* 1: unregistered id (invalid char_id)
* 2: Error while writing into skill table
*/
int brokk_teach_skills(bot_skill_bundle& skill_bundle, int& char_id) {
int skill_count = 0;
// Check if the character already exists
mmo_charakter existing_char;
if (!charakters->load_num(charakters, &existing_char, char_id)) {
ShowError("Attempt to add an skill to non-existing character (char_id: %d)\n", char_id);
return 1; // Character does not exist
}
std::string char_name = existing_char.name;
for (auto& skill : skill_bundle.skills) {
// Create entry in the skill database
if (!skills->create(skills, &skill)) {
ShowError("Failed to create skill entry (char_id: %d, skill_id: %d)\n", skill.char_id, skill.skill_id);
return 2; // skill entry creation failed
}
// Log success single skill
ShowNotice("Skill %s added to character char_id: %d)\n", convert_skill_id_to_str(skill.skill_id), skill.char_id);
skill_count++;
}
// Log success
ShowStatus("Successfully applied %d Skills to character (name: %s, char_id: %d)\n", skill_count, char_name.c_str(), char_id);
return -1; // Success
} }
void BrokkServer::finalize() { void BrokkServer::finalize() {
@ -730,7 +706,7 @@ void BrokkServer::finalize() {
// brokklog_final(); // brokklog_final();
// Attempt to delete the bot account on shutdown // Attempt to delete the bot account on shutdown
bot_account_cleanup(); //bot_account_cleanup();
if (db) { // destroy account engine if (db) { // destroy account engine
db->destroy(db); db->destroy(db);
@ -768,7 +744,10 @@ void BrokkServer::finalize() {
void BrokkServer::handle_shutdown() { void BrokkServer::handle_shutdown() {
ShowStatus("Shutting down...\n"); ShowStatus("Shutting down...\n");
// Attempt to delete the bot account on shutdown
bot_account_cleanup();
flush_fifos(); flush_fifos();
} }
@ -785,6 +764,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
// Initialize Yaml-DBs // Initialize Yaml-DBs
bot_read_yaml_dbs(); bot_read_yaml_dbs();
init_bot_skill_tree_db();
// read brokk-server configuration // read brokk-server configuration
brokk_set_defaults(); brokk_set_defaults();
@ -796,8 +776,8 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
return false; return false;
} }
/**/ //debug: display skills of gm acc
//debug skills von gm acc aufzählen /*
SkillDBIterator* iter_skills = skills->iterator(skills, 150000); SkillDBIterator* iter_skills = skills->iterator(skills, 150000);
struct bot_skill skill; struct bot_skill skill;
while (iter_skills->next(iter_skills, 150000, &skill)) { while (iter_skills->next(iter_skills, 150000, &skill)) {
@ -806,7 +786,21 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
} }
// Destroy the iterator after use // Destroy the iterator after use
iter_skills->destroy(iter_skills); iter_skills->destroy(iter_skills);
*/
/*
//Demo-Skill tree
//TODO Eintragen in Job-Profil -> welche Skills gelernt werden sollen und Mechanik bauen für plausibles auto-skill erlernen
std::shared_ptr<bot_skill_tree> tree = bot_skill_tree_db.find(JOB_MAGE);
for (const auto& skill : tree->skills){
ShowDebug("Skill Found in tree: %s \n",convert_skill_id_to_str(skill.first));
if (skill.first == MG_SAFETYWALL) {
ShowDebug("skill max lvl : %d \n", skill.second->max_lv);
for (const auto needed_skill : skill.second->need) {
ShowDebug("requires skill %s on level %d\n", convert_skill_id_to_str(needed_skill.first), needed_skill.second);
}
}
}*/
// initialize logging // initialize logging
//if (brokk_config.log_brokk) //if (brokk_config.log_brokk)
@ -847,7 +841,6 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
const char* userid = userid_str.c_str(); const char* userid = userid_str.c_str();
const std::string pass = bot["pass"]; const std::string pass = bot["pass"];
const char sex = bot["sex"].get<std::string>()[0]; // Extract single char from string const char sex = bot["sex"].get<std::string>()[0]; // Extract single char from string
const std::string last_ip = bot["last_ip"];
// Debug: Print the entire bot entry // Debug: Print the entire bot entry
//ShowInfo("Processing bot: %s\n", bot.dump(4).c_str()); //ShowInfo("Processing bot: %s\n", bot.dump(4).c_str());
@ -860,7 +853,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
//ShowStatus("Processing bot: userid=%s, pass=%s, sex=%c\n", userid, pass.c_str(), sex); //ShowStatus("Processing bot: userid=%s, pass=%s, sex=%c\n", userid, pass.c_str(), sex);
// Call brokk_mmo_auth_new to handle account creation // Call brokk_mmo_auth_new to handle account creation
if (!brokk_mmo_auth_new(userid, pass.c_str(), sex, last_ip.c_str(), true)) { if (!brokk_mmo_auth_new(userid, pass.c_str(), sex, true)) {
ShowError("Failed to create bot account (userid: %s)\n", userid); ShowError("Failed to create bot account (userid: %s)\n", userid);
continue; // Skip to the next bot account continue; // Skip to the next bot account
} }
@ -874,110 +867,141 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
} }
uint32 account_id = acc.account_id; uint32 account_id = acc.account_id;
// Iterate over each character for this bot account // Iterate over each character for this bot account
if (bot.contains("characters")) {
const auto& characters = bot["characters"];
//TODO ähnlichen Test für skills einbauen
for (const auto& character : characters) { if (bot.contains("characters")) {
const std::string char_name = character["name"]; const auto& json_characters = bot["characters"];
const int slot = character["slot"]; //ShowDebug("StartChar Parsing");
for (const auto& json_character : json_characters) { //Iterate every declared character and try to create it
//ShowDebug("char_json:\n%s", character.dump().c_str());
mmo_charakter new_char{};
//Step1 Meta-Data about character (allways have to be present/not bound to profile)
const std::string char_name = json_character["name"];
safestrncpy(new_char.name, char_name.c_str(), NAME_LENGTH);
std::string sex_str = json_character["sex"];
new_char.sex = sex_str[0];
new_char.char_num = json_character["slot"];
new_char.zeny = json_character["zeny"];
new_char.inventory_slots = 100;
// Decode Job Value
std::string job_id = json_character["start_job"];
new_char.class_ = convert_jobid_to_number(job_id.c_str());
if (new_char.class_ == -1) {
job_id = "JOB_NOVICE";
new_char.class_ = JOB_NOVICE;
}
// Step2: build data of character
new_char.account_id = account_id;
new_char.char_id = -1;
prepare_character_build_data(new_char, json_character, bot_job_db);
const int base_level = character["base_level"]; // Step3: appearance of character
const int job_level = character["job_level"]; prepare_character_appearance_data(new_char, json_character);
const int hair_style = character["hair_style"]; // Step4: location data of character
const int hair_color = character["hair_color"]; prepare_character_location_data(new_char, json_character);
//TODO: Clothing/Style Randomizer hier einbauen
//Decode Job Value (is given as the ID not the number behind the ID for readability) // All char table related data is collected and can be saved to allow further steps (skills/items/equip)
const std::string job_id = character["start_job"]; int new_char_id = 0;
const int start_job = convert_jobid_to_number(job_id.c_str()); try {
if (start_job == -1) { if (!brokk_create_character(new_char, new_char_id, true)) {
const int start_job = JOB_NOVICE;
if (new_char_id < 1) {
ShowError("Failed to create character: %s\n", new_char.name);
continue;
}
else {
ShowNotice("Atempt to create existing character : % s (id: %d)\n", new_char.name, new_char_id);
continue;
}
}// otherwise character is correctly and newly created
}
catch (const std::exception& e) {
ShowError("Character creation for '%s 'failed: %s\n",new_char.name, e.what());
continue;
} }
std::shared_ptr<bot_job_info> job = bot_job_db.find(start_job); //TODO Ab hier debuggen warum bei EvaExample bei neuanlage crashed
// Step5: Skills
// Prepare skills inside a skill bundle
bot_skill_bundle skill_bundle{};
if (base_level > bot_job_db.get_maxBaseLv(start_job) or job_level > bot_job_db.get_maxJobLv(start_job)) prepare_skill_bundle(skill_bundle, new_char_id, json_character);
{
//ShowNotice("Error in bot data. Inplausible baselevel (max=%s) or joblevel (max=%s) (account: %s, charname: $s, baselevel: %d, joblevel: %d)\n", bot_job_db.get_maxBaseLv(start_job), bot_job_db.get_maxJobLv(start_job), userid, char_name, base_level, job_level);
ShowNotice("Unplausible Base-level/Job-level");
continue; // Inplausible data
}
// Use job_id as reference for stat_builds // All skill table related data is collected and can be saved
const std::string build_profile = character["char_build_profile"]; try {
// Prep with default if (!brokk_teach_skills(skill_bundle, new_char_id)) {
int str = 1; ShowError("Failed to create skills entry for: %s\n", new_char.name);
int agi = 1;
int vit = 1;
int int_ = 1;
int dex = 1;
int luk = 1;
if (build_profile == "CUSTOM") {
// When a CUSTOM build is entered, stats key must be present
if (!character.contains("stats")) {
// character params in json are malformed. Stats for CUSTOM build are missing
ShowNotice("Error: Could not create character %s . Stats are missing.", char_name);
continue; continue;
} }
const auto& char_stats = character["stats"];
str = char_stats["str"];
agi = char_stats["agi"];
vit = char_stats["vit"];
int_ = char_stats["int"];
dex = char_stats["dex"];
luk = char_stats["luk"];
} }
else { catch (const std::exception& e) {
// Open the JSON file ShowError("skill creation for '%s 'failed: %s\n", new_char.name, e.what());
std::string job_profile_file_path = "conf/bot_profiles/"; continue;
job_profile_file_path += job_id.c_str(); }
job_profile_file_path += "_profiles.json";
std::ifstream stat_profile_file(job_profile_file_path);
if (!stat_profile_file.is_open()) {
ShowError("Failed to open %s\n", job_profile_file_path.c_str());
return false;
}
// Parse the JSON file ShowNotice("Skills successfully teached to character (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex);
json json_stat_profiles;
try {
stat_profile_file >> json_stat_profiles;
} //Step4: Items
catch (json::parse_error& e) { //TODO Testweise folgende ItemIDs einbauen für Debug:
ShowError("Error parsing %s for job: %s\n", job_profile_file_path.c_str(), job_id.c_str()); // 1950, 2627 , 4040
return false; //ShowNotice("Items successfully handed out to character (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex);
}
// Close file after done with it //Step5: Equipment
stat_profile_file.close();
//ShowNotice("Gear/Weapons successfully equiped (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex);
// Read profile data and convert to stats ShowStatus("Bot-Character creation and administration completed (name: %s, char_id: %d, sex: %c)\n", new_char.name, new_char.char_id, new_char.sex);
if (!json_stat_profiles.contains(build_profile)) {
ShowError("Error build profile %s does not exists. Skipping char...\n", build_profile.c_str()); /*****
return false; * Validating/Applying skill build
*****
// DEBUG: check skill plausibility
const auto& skill_bundle = character["skills"];
ShowDebug("Skill bundle: %s\n", skill_bundle.dump(4).c_str()); // Ensure `skill_bundle` is structured as expected
for (const auto& skill : skill_bundle.items()) {
ShowDebug("Skill: %s", skill.key().c_str());
std::unordered_map<uint16, uint16> required_skill;
//skill >> required_skill;
std::string skill_str = skill.key();
int skill_id = convert_skill_str_to_id(skill_str.c_str());
if (skill_id < 0) { // Assuming negative IDs are invalid
ShowError("Invalid skill ID for skill string: %s\n", skill_str.c_str());
continue; // Skip this iteration
} }
const auto& stats = json_stat_profiles[build_profile]; std::unordered_map<uint16, uint16> parsed_skill_bundle;
str = stats["str"]; for (const auto& skill : skill_bundle.items()) {
agi = stats["agi"]; uint16 skill_id = convert_skill_str_to_id(skill.key().c_str());
vit = stats["vit"]; uint16 level = skill.value(); // Assuming the value represents the skill level
int_ = stats["int"]; parsed_skill_bundle[skill_id] = level;
dex = stats["dex"]; }
luk = stats["luk"];
}
// DEBUG: Show character details bool skill_plausible = check_skill_requirements(start_job, skill_id, parsed_skill_bundle);
//ShowStatus("Creating character: name=%s, slot=%d, start_job=%d\n", char_name.c_str(), slot, start_job); if (skill_plausible) {
ShowDebug("all requirements met for skill %s \n", skill.key());
}
else {
ShowDebug("some requirements not met for skill %s , Cancel creation process?\n", skill.key());
continue;
}
}*/
// Create the character for this account // Create the character for this account
// TODO rebuild to:
// if (!brokk_create_character(new_char, skill_build_data, item_data, equip_data true))
/*
if (!brokk_create_character(account_id, char_name.c_str(), slot, base_level, job_level, str, agi, vit, int_, dex, luk, hair_style, hair_color, start_job, sex, true)) if (!brokk_create_character(account_id, char_name.c_str(), slot, base_level, job_level, str, agi, vit, int_, dex, luk, hair_style, hair_color, start_job, sex, true))
{ {
ShowError("Failed to create charakter (char_name: %s)\n", char_name); ShowError("Failed to create charakter (char_name: %s)\n", char_name);
continue; // Skip to the next charakter continue; // Skip to the next charakter
} }*/
} }
} }
@ -988,7 +1012,7 @@ bool BrokkServer::initialize(int argc, char* argv[]) {
} }
// Other initialization logic // Other initialization logic
setup_periodic_cleanup_timer(); // Set up the cleanup timer //setup_periodic_cleanup_timer(); // Set up the cleanup timer
ShowStatus("The brokk-server is " CL_GREEN "ready" CL_RESET " (Server is listening on the port %u).\n\n", brokk_config.brokk_port); ShowStatus("The brokk-server is " CL_GREEN "ready" CL_RESET " (Server is listening on the port %u).\n\n", brokk_config.brokk_port);
//brokk_log(0, "brokk server", 100, "brokk server started"); //brokk_log(0, "brokk server", 100, "brokk server started");

@ -11,6 +11,9 @@
#include <common/mmo.hpp> // NAME_LENGTH,SEX_* #include <common/mmo.hpp> // NAME_LENGTH,SEX_*
#include <common/timer.hpp> #include <common/timer.hpp>
#include <config/core.hpp> #include <config/core.hpp>
#include "charakters.hpp"
#include "char_creation_helpers.hpp"
#include "helper_skills.hpp"
using rathena::server_core::Core; using rathena::server_core::Core;
using rathena::server_core::e_core_type; using rathena::server_core::e_core_type;
@ -57,7 +60,7 @@ struct Brokk_Config {
extern struct Brokk_Config brokk_config; extern struct Brokk_Config brokk_config;
// Bot Account creation // Bot Account creation
int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const char* last_ip, const bool auto_generated, int creator_id, const char* creator_name); int brokk_mmo_auth_new(const char* userid, const char* pass, const char sex, const bool auto_generated, int creator_id, const char* creator_name);
int brokk_mmo_auth_delete_str(const char* userid); int brokk_mmo_auth_delete_str(const char* userid);
int brokk_mmo_auth_delete_num(uint32 account_id); int brokk_mmo_auth_delete_num(uint32 account_id);
bool brokk_config_read(const char* cfgName, bool normal); bool brokk_config_read(const char* cfgName, bool normal);
@ -65,6 +68,10 @@ void brokk_set_defaults();
void setup_periodic_cleanup_timer(); void setup_periodic_cleanup_timer();
void bot_account_cleanup(); void bot_account_cleanup();
bool brokk_initialize_dbs(); bool brokk_initialize_dbs();
bool brokk_create_character(mmo_charakter& charakter, int& char_id, const bool auto_generated);
int brokk_teach_skills(bot_skill_bundle& skill_bundle, int& char_id);
//int brokk_add_item(container_struct& container);
//int brokk_apply_equip(container_struct& container);
#endif /* BROKK_HPP */ #endif /* BROKK_HPP */

@ -6,6 +6,7 @@
#include "char_creation_helpers.hpp" #include "char_creation_helpers.hpp"
#include <cstdlib> #include <cstdlib>
#include <sstream>
#include <array> #include <array>
#include <common/malloc.hpp> #include <common/malloc.hpp>
@ -15,6 +16,8 @@
#include "charakters.hpp" #include "charakters.hpp"
#include <iostream>
#include <fstream>
using namespace rathena; using namespace rathena;
@ -652,6 +655,317 @@ static unsigned int pc_calc_basesp(uint16 level, uint16 job_id) {
return (unsigned int)base_sp; return (unsigned int)base_sp;
} }
void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& character, BotJobDatabase& bot_job_db) {
// Validate and apply Build
std::string build_profile = character["char_build_profile"];
if (build_profile == "CUSTOM") {
if (!character.contains("build")) {
throw std::runtime_error("Build data is missing for CUSTOM build profile.");
}
const auto& build_data = character["build"];
new_char.base_level = build_data["base_level"];
new_char.job_level = build_data["job_level"];
std::string job_str = character["start_job"];
int job_id = convert_jobid_to_number(job_str.c_str());
//Base/Job EXP values (need to be set to technically calculate xp correctly)
uint64 base_exp = 0;
uint64 job_exp = 0;
if (new_char.base_level > 1) {
base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level - 1);
}
if (new_char.job_level > 1) {
job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level - 1);
}
new_char.base_exp = base_exp;
new_char.job_exp = job_exp;
new_char.inventory_slots = 100; //Default
//TODO auslagern in calc fn
new_char.max_hp = 40;
new_char.hp = 40;
new_char.max_sp = 10;
new_char.sp = 10;
new_char.max_ap = 0;
new_char.ap = 0;
new_char.str = build_data["str"];
new_char.agi = build_data["agi"];
new_char.vit = build_data["vit"];
new_char.int_ = build_data["int"];
new_char.dex = build_data["dex"];
new_char.luk = build_data["luk"];
new_char.pow = build_data["pow"];
new_char.sta = build_data["sta"];
new_char.wis = build_data["wis"];
new_char.spl = build_data["spl"];
new_char.con = build_data["con"];
new_char.crt = build_data["crt"];
}
else {
// Load Build Profile
std::string job_str = character["start_job"];
int job_id = convert_jobid_to_number(job_str.c_str());
std::string job_profile_file_path = "conf/bot_profiles/build_profiles/" + job_str + "_build_profile.json";
nlohmann::json json_build_profiles;
// Try to Open Build Profile
std::ifstream build_profile_file(job_profile_file_path);
if (!build_profile_file.is_open()) {
ShowWarning("Failed to open build_profile_file %s. Trying to load DEFAULT instead.", job_profile_file_path);
job_profile_file_path = "conf/bot_profiles/build_profiles/DEFAULT_build_profile.json";
build_profile_file.open(job_profile_file_path);
if (!build_profile_file.is_open()) {
throw std::runtime_error("Failed to open both job and default profiles.");
}
}
// Parse Profile Data
try {
build_profile_file >> json_build_profiles;
}
catch (const nlohmann::json::parse_error& e) {
throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what()));
}
if (!json_build_profiles.contains(build_profile)) {
// If build profile is not found, default to INIT
build_profile = "INIT";
}
const auto& profile = json_build_profiles[build_profile];
new_char.base_level = profile["base_level"];
new_char.job_level = profile["job_level"];
//Check if chosen max level exceeds base level or job level maximum range
if (new_char.base_level > bot_job_db.get_maxBaseLv(job_id) or new_char.job_level > bot_job_db.get_maxJobLv(job_id))
{
throw std::runtime_error("Error parsing build profile JSON: base_level or job_level exceeding allowed max_level");
}
//Base/Job EXP values (need to be set to technically calculate xp correctly)
uint32 base_exp = bot_job_db.get_baseExp(job_id, new_char.base_level);
uint32 job_exp = bot_job_db.get_jobExp(job_id, new_char.job_level);
new_char.base_exp = base_exp;
new_char.job_exp = job_exp;
//Set stats
new_char.str = profile["str"];
new_char.agi = profile["agi"];
new_char.vit = profile["vit"];
new_char.int_ = profile["int"];
new_char.dex = profile["dex"];
new_char.luk = profile["luk"];
new_char.pow = profile["pow"];
new_char.sta = profile["sta"];
new_char.wis = profile["wis"];
new_char.spl = profile["spl"];
new_char.con = profile["con"];
new_char.crt = profile["crt"];
}
//ShowDebug("Character: \n%s",dump_mmo_charakter(new_char));
}
void prepare_character_appearance_data(mmo_charakter& new_char, const nlohmann::json& character){
// Validate and apply appearance data
std::string appearance_profile = character["char_appearance_profile"];
if (appearance_profile == "CUSTOM") {
if (!character.contains("appearance")) {
throw std::runtime_error("appearance data is missing for CUSTOM appearance profile.");
}
const auto& appearance_data = character["appearance"];
//Set hair style
uint16 hair_style = appearance_data["hair_style"]; //Default max value = 23
if (hair_style < 0) {
//Negative style value => random hair style
int random_hair_style = 1 + (rand() % 23);
new_char.hair = random_hair_style;
}
else {
new_char.hair = hair_style;
}
// Set hair color
uint16 hair_color = appearance_data["hair_color"]; //Default max value = 8
if (hair_color < 0) {
//Negative color value => random hair color
int random_hair_color = 1 + (rand() % 8);
new_char.hair_color = random_hair_color;
}
else {
new_char.hair_color = hair_color;
}
// Set clothes color
uint16 clothes_color = appearance_data["clothes_color"]; //Default max value = ?
if (clothes_color < 0) {
//Negative color value => random clothes color
int random_clothes_color = 0;
new_char.clothes_color = random_clothes_color;
}
else {
new_char.clothes_color = clothes_color;
}
}
else {
// Load appearance Profile
std::string appearance_profile_file_path = "conf/bot_profiles/appearance_profiles.json";
nlohmann::json json_appearance_profiles;
// Try to Open appearance Profile
std::ifstream appearance_profile_file(appearance_profile_file_path);
if (!appearance_profile_file.is_open()) {
std::string err_msg = ("Failed to open appearance_profile_file %s.", appearance_profile_file_path);
throw std::runtime_error(err_msg.c_str());
}
// Parse Profile Data
try {
appearance_profile_file >> json_appearance_profiles;
}
catch (const nlohmann::json::parse_error& e) {
throw std::runtime_error("Error parsing appearance profile JSON: " + std::string(e.what()));
}
if (!json_appearance_profiles.contains(appearance_profile)) {
// If build profile is not found, default to INIT
appearance_profile = "INIT";
}
const auto& profile = json_appearance_profiles[appearance_profile];
//Set hair style
uint16 hair_style = profile["hair_style"]; //Default max value = 23
if (hair_style < 0) {
//Negative style value => random hair style
int random_hair_style = 1 + (rand() % 22);
new_char.hair = random_hair_style;
}
else {
new_char.hair = hair_style;
}
// Set hair color
uint16 hair_color = profile["hair_color"]; //Default max value = 8
if (hair_color < 0) {
//Negative color value => random hair color
//int random_hair_color = 1 + (rand() % 7);
int random_hair_color = 1;
new_char.hair_color = random_hair_color;
}
else {
new_char.hair_color = hair_color;
}
// Set clothes color
uint16 clothes_color = profile["clothes_color"]; //Default max value = ?
if (clothes_color < 0) {
//Negative color value => random clothes color
int random_clothes_color = 0;
new_char.clothes_color = random_clothes_color;
}
else {
new_char.clothes_color = clothes_color;
}
}
}
void prepare_character_location_data(mmo_charakter& new_char, const nlohmann::json& character) {
// Validate and apply location data
std::string location_profile = character["char_location_profile"];
if (location_profile == "CUSTOM") {
if (!character.contains("location")) {
throw std::runtime_error("location data is missing for CUSTOM location profile.");
}
const auto& location_data = character["location"];
//Set last map
std::string last_map = location_data["last_map"];
safestrncpy(new_char.last_map, last_map.c_str(), sizeof(new_char.last_map));
new_char.last_x = location_data["last_x"];
new_char.last_y = location_data["last_y"];
//Set saved map
std::string save_map = location_data["save_map"];
safestrncpy(new_char.save_map, save_map.c_str(), sizeof(new_char.save_map));
new_char.save_x = location_data["save_x"];
new_char.save_y = location_data["save_y"];
//Set Instance nr
new_char.last_instanceid = location_data["last_instanceid"];
}
else {
// Load location Profile
std::string location_profile_file_path = "conf/bot_profiles/location_profiles.json";
nlohmann::json json_location_profiles;
// Try to Open location Profile
std::ifstream location_profile_file(location_profile_file_path);
if (!location_profile_file.is_open()) {
std::string err_msg = ("Failed to open location %s.", location_profile_file_path);
throw std::runtime_error(err_msg.c_str());
}
// Parse Profile Data
try {
location_profile_file >> json_location_profiles;
}
catch (const nlohmann::json::parse_error& e) {
throw std::runtime_error("Error parsing location profile JSON: " + std::string(e.what()));
}
if (!json_location_profiles.contains(location_profile)) {
// If build profile is not found, default to INIT
location_profile = "INIT";
}
const auto& profile = json_location_profiles[location_profile];
//Set last map
std::string last_map = profile["last_map"];
safestrncpy(new_char.last_map, last_map.c_str(), sizeof(new_char.last_map));
new_char.last_x = profile["last_x"];
new_char.last_y = profile["last_y"];
//Set saved map
std::string save_map = profile["save_map"];
safestrncpy(new_char.save_map, save_map.c_str(), sizeof(new_char.save_map));
new_char.save_x = profile["save_x"];
new_char.save_y = profile["save_y"];
//Set Instance nr
new_char.last_instanceid = profile["last_instanceid"];
}
}
/*========================================== /*==========================================
* Convert's from the client's lame Job ID system * Convert's from the client's lame Job ID system
* to the map server's 'makes sense' system. [Skotlex] * to the map server's 'makes sense' system. [Skotlex]
@ -679,7 +993,7 @@ uint64 pc_jobid2mapid(unsigned short b_class)
case JOB_OKTOBERFEST: return BOT_MAPID_OKTOBERFEST; case JOB_OKTOBERFEST: return BOT_MAPID_OKTOBERFEST;
case JOB_SUMMER2: return BOT_MAPID_SUMMER2; case JOB_SUMMER2: return BOT_MAPID_SUMMER2;
//2-1 Jobs //2-1 Jobs
case JOB_SUPER_NOVICE: return BOT_MAPID_SUPER_NOVICE; case JOB_SUPER_NOVICE: return BOT_MAPID_SUPER_NOVICE;
case JOB_KNIGHT: return BOT_MAPID_KNIGHT; case JOB_KNIGHT: return BOT_MAPID_KNIGHT;
case JOB_WIZARD: return BOT_MAPID_WIZARD; case JOB_WIZARD: return BOT_MAPID_WIZARD;
case JOB_HUNTER: return BOT_MAPID_HUNTER; case JOB_HUNTER: return BOT_MAPID_HUNTER;
@ -1420,7 +1734,56 @@ void bot_read_yaml_dbs(void) {
aFree(dbsubpath2); } aFree(dbsubpath2); }
// Reset and read skilltree - needs to be read after pc_readdb_job_exp to get max base and job levels // Reset and read skilltree - needs to be read after pc_readdb_job_exp to get max base and job levels
//skill_tree_db.reload(); //bot_skill_tree_db.reload();
bot_statpoint_db.load(); bot_statpoint_db.load();
} }
std::string dump_mmo_charakter(const mmo_charakter& charakter) {
std::string dump;
dump += "Character Dump:\n";
dump += "-----------------\n";
dump += "char_id: " + std::to_string(charakter.char_id) + "\n";
dump += "account_id: " + std::to_string(charakter.account_id) + "\n";
dump += "char_num: " + std::to_string(charakter.char_num) + "\n";
dump += "name: " + std::string(charakter.name) + "\n";
dump += "class: " + std::to_string(charakter.class_) + "\n";
dump += "base_level: " + std::to_string(charakter.base_level) + "\n";
dump += "job_level: " + std::to_string(charakter.job_level) + "\n";
dump += "base_exp: " + std::to_string(charakter.base_exp) + "\n";
dump += "job_exp: " + std::to_string(charakter.job_exp) + "\n";
dump += "zeny: " + std::to_string(charakter.zeny) + "\n";
dump += "Stats:\n";
dump += " str: " + std::to_string(charakter.str) + "\n";
dump += " agi: " + std::to_string(charakter.agi) + "\n";
dump += " vit: " + std::to_string(charakter.vit) + "\n";
dump += " int: " + std::to_string(charakter.int_) + "\n";
dump += " dex: " + std::to_string(charakter.dex) + "\n";
dump += " luk: " + std::to_string(charakter.luk) + "\n";
dump += "Health/Mana:\n";
dump += " max_hp: " + std::to_string(charakter.max_hp) + "\n";
dump += " hp: " + std::to_string(charakter.hp) + "\n";
dump += " max_sp: " + std::to_string(charakter.max_sp) + "\n";
dump += " sp: " + std::to_string(charakter.sp) + "\n";
dump += "Additional Info:\n";
dump += " curr_map: " + std::string(charakter.last_map) + "\n";
dump += " curr_x: " + std::to_string(charakter.last_x) + "\n";
dump += " curr_y: " + std::to_string(charakter.last_y) + "\n";
dump += " save_map: " + std::string(charakter.save_map) + "\n";
dump += " save_x: " + std::to_string(charakter.save_x) + "\n";
dump += " save_y: " + std::to_string(charakter.save_y) + "\n";
dump += "Sex: " + std::string(1, charakter.sex) + "\n";
dump += "Last Login: " + std::string(charakter.last_login) + "\n";
dump += "Hair Style: " + std::to_string(charakter.hair) + "\n";
dump += "Hair Color: " + std::to_string(charakter.hair_color) + "\n";
return dump;
}

@ -6,11 +6,14 @@
#ifndef CHAR_CREATION_HELPERS_HPP #ifndef CHAR_CREATION_HELPERS_HPP
#define CHAR_CREATION_HELPERS_HPP #define CHAR_CREATION_HELPERS_HPP
#include "json.hpp"
#include <common/mmo.hpp> // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
#include <string> #include <string>
#include <cstdint> #include <cstdint>
#include <common/cbasetypes.hpp> #include <common/cbasetypes.hpp>
#include <common/database.hpp> #include <common/database.hpp>
#include <common/mmo.hpp> // JOB_*, MAX_FAME_LIST, struct fame_list, struct mmo_charstatus
#include "charakters.hpp" #include "charakters.hpp"
/* /*
* Constants for yaml dbs * Constants for yaml dbs
@ -366,4 +369,9 @@ extern BotJobDatabase bot_job_db;
void bot_read_yaml_dbs(void); void bot_read_yaml_dbs(void);
void prepare_character_build_data(mmo_charakter& new_char, const nlohmann::json& character, BotJobDatabase& bot_job_db);
void prepare_character_appearance_data(mmo_charakter& new_char, const nlohmann::json& character);
void prepare_character_location_data(mmo_charakter& new_char, const nlohmann::json& character);
std::string dump_mmo_charakter(const mmo_charakter& character);
#endif /* CHAR_CREATION_HELPERS_HPP */ #endif /* CHAR_CREATION_HELPERS_HPP */

@ -284,15 +284,13 @@ static bool char_db_sql_create(CharDB* self, struct mmo_charakter* charakter) {
Sql_FreeResult(sql_handle_char); Sql_FreeResult(sql_handle_char);
char_id = max((uint32_t)START_CHAR_NUM, char_id); char_id = max((uint32_t)START_CHAR_NUM, char_id);
} }
// zero value is prohibited // zero value is prohibited
if(char_id == 0 ) if(char_id == 0 )
return false; return false;
// absolute maximum // absolute maximum
if(char_id > END_ACCOUNT_NUM ) if(char_id > END_ACCOUNT_NUM )
return false; return false;
// insert the data into the database // insert the data into the database
charakter->char_id = char_id; charakter->char_id = char_id;
return mmo_char_auth_tosql(db, charakter, true); return mmo_char_auth_tosql(db, charakter, true);
@ -671,8 +669,8 @@ static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* char
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 32, SQLDT_INT32, (void*)&charakter->option, sizeof(charakter->option)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 32, SQLDT_INT32, (void*)&charakter->option, sizeof(charakter->option))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 33, SQLDT_INT8, (void*)&charakter->karma, sizeof(charakter->karma)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 33, SQLDT_INT8, (void*)&charakter->karma, sizeof(charakter->karma))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 34, SQLDT_INT16, (void*)&charakter->manner, sizeof(charakter->manner)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 34, SQLDT_INT16, (void*)&charakter->manner, sizeof(charakter->manner))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 35, SQLDT_INT8, (void*)&charakter->hair, sizeof(charakter->hair)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 35, SQLDT_SHORT, (void*)&charakter->hair, sizeof(charakter->hair))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 36, SQLDT_INT16, (void*)&charakter->hair_color, sizeof(charakter->hair_color)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 36, SQLDT_SHORT, (void*)&charakter->hair_color, sizeof(charakter->hair_color))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 37, SQLDT_INT16, (void*)&charakter->clothes_color, sizeof(charakter->clothes_color)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 37, SQLDT_INT16, (void*)&charakter->clothes_color, sizeof(charakter->clothes_color))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 38, SQLDT_INT16, (void*)&charakter->body, sizeof(charakter->body)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 38, SQLDT_INT16, (void*)&charakter->body, sizeof(charakter->body))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 39, SQLDT_INT16, (void*)&charakter->weapon, sizeof(charakter->weapon)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 39, SQLDT_INT16, (void*)&charakter->weapon, sizeof(charakter->weapon))
@ -731,8 +729,8 @@ static bool mmo_char_auth_tosql(CharDB_SQL* db, const struct mmo_charakter* char
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_INT16, (void*)&charakter->int_, sizeof(charakter->int_)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 10, SQLDT_INT16, (void*)&charakter->int_, sizeof(charakter->int_))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_INT16, (void*)&charakter->dex, sizeof(charakter->dex)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 11, SQLDT_INT16, (void*)&charakter->dex, sizeof(charakter->dex))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_INT16, (void*)&charakter->luk, sizeof(charakter->luk)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 12, SQLDT_INT16, (void*)&charakter->luk, sizeof(charakter->luk))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_INT8, (void*)&charakter->hair, sizeof(charakter->hair)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 13, SQLDT_SHORT, (void*)&charakter->hair, sizeof(charakter->hair))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_INT16, (void*)&charakter->hair_color, sizeof(charakter->hair_color)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 14, SQLDT_SHORT, (void*)&charakter->hair_color, sizeof(charakter->hair_color))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_INT, (void*)&charakter->max_hp, sizeof(charakter->max_hp)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 15, SQLDT_INT, (void*)&charakter->max_hp, sizeof(charakter->max_hp))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_INT, (void*)&charakter->hp, sizeof(charakter->hp)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 16, SQLDT_INT, (void*)&charakter->hp, sizeof(charakter->hp))
|| SQL_SUCCESS != SqlStmt_BindParam(stmt, 17, SQLDT_INT, (void*)&charakter->max_sp, sizeof(charakter->max_sp)) || SQL_SUCCESS != SqlStmt_BindParam(stmt, 17, SQLDT_INT, (void*)&charakter->max_sp, sizeof(charakter->max_sp))

@ -10,6 +10,10 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <unordered_map> #include <unordered_map>
#include <array>
#include <iostream>
#include <fstream>
#include <common/malloc.hpp> #include <common/malloc.hpp>
#include <common/mmo.hpp> #include <common/mmo.hpp>
@ -17,9 +21,13 @@
#include <common/socket.hpp> #include <common/socket.hpp>
#include <common/sql.hpp> #include <common/sql.hpp>
#include <common/strlib.hpp> #include <common/strlib.hpp>
#include <common/utilities.hpp>
#include <common/utils.hpp>
#include <map/skill.hpp> #include <map/skill.hpp>
#include "brokk.hpp" // brokk_config #include "brokk.hpp" // brokk_config
#include "char_creation_helpers.hpp"
using namespace rathena;
/// global defines /// global defines
@ -48,6 +56,8 @@ typedef struct SkillDBIterator_SQL {
int last_skill_id; int last_skill_id;
} SkillDBIterator_SQL; } SkillDBIterator_SQL;
BotSkillTreeDatabase bot_skill_tree_db;
/// internal functions /// internal functions
static bool bot_skill_db_sql_init(SkillDB* self); static bool bot_skill_db_sql_init(SkillDB* self);
static void bot_skill_db_sql_destroy(SkillDB* self); static void bot_skill_db_sql_destroy(SkillDB* self);
@ -62,6 +72,37 @@ static void bot_skill_db_sql_iter_destroy(SkillDBIterator* self);
static bool bot_skill_db_sql_iter_next(SkillDBIterator* self, const uint32 char_id, struct bot_skill* skill); static bool bot_skill_db_sql_iter_next(SkillDBIterator* self, const uint32 char_id, struct bot_skill* skill);
static bool bot_skill_db_fromsql(SkillDB_SQL* db, struct bot_skill* skill, uint32 char_id, uint32 skill_id); static bool bot_skill_db_fromsql(SkillDB_SQL* db, struct bot_skill* skill, uint32 char_id, uint32 skill_id);
static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, const uint32 char_id, bool is_new); static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, const uint32 char_id, bool is_new);
/*
static bool skill_check(uint16 id) {
if (id == 0 || skill_get_index(id) == 0)
return false;
return true;
}
#define skill_get(id, var) do {\
if (!skill_check(id))\
return 0;\
return var;\
} while(0)*/
//int skill_get_max(uint16 skill_id) { skill_get(skill_id, skill_db.find(skill_id)->max); }
/**
* Get skill id from name
* @param name
* @return Skill ID of the skill, or 0 if not found.
*
uint16 skill_name2id(const char* name) {
if (name == nullptr)
return 0;
for (const auto& it : bot_skill_db) {
if (strcmpi(it.second->name, name) == 0)
return it.first;
}
return 0;
}*/
/// public constructor /// public constructor
SkillDB* skill_db_sql(void) { SkillDB* skill_db_sql(void) {
@ -491,6 +532,545 @@ static bool bot_skill_db_tosql(SkillDB_SQL* db, const struct bot_skill* skill, c
return result; return result;
} }
/*********
* Skill Tree Yaml Database functions
*********/
std::shared_ptr<bot_skill_tree_entry> BotSkillTreeDatabase::get_skill_data(int class_, uint16 skill_id) {
std::shared_ptr<bot_skill_tree> tree = this->find(class_);
if (tree != nullptr)
return util::umap_find(tree->skills, skill_id);
return nullptr;
}
const std::string BotSkillTreeDatabase::getDefaultLocation() {
return std::string(db_path) + "/skill_tree.yml";
}
/**
* Reads and parses an entry from the skill_tree.
* @param node: YAML node containing the entry.
* @return count of successfully parsed rows
*/
uint64 BotSkillTreeDatabase::parseBodyNode(const ryml::NodeRef& node) {
std::string job_name;
if (!this->asString(node, "Job", job_name))
return 0;
//int64 constant;
std::string job_name_constant = "JOB_" + job_name;
/*
if (!script_get_constant(job_name_constant.c_str(), &constant)) {
this->invalidWarning(node["Job"], "Invalid job %s.\n", job_name.c_str());
return 0;
}
uint16 job_id = static_cast<uint16>(constant);
*/
uint16 job_id = convert_jobid_to_number(job_name_constant.c_str());
std::shared_ptr<bot_skill_tree> tree = this->find(job_id);
bool exists = tree != nullptr;
if (!exists)
tree = std::make_shared<bot_skill_tree>();
if (this->nodeExists(node, "Inherit")) {
const ryml::NodeRef& InheritNode = node["Inherit"];
for (const auto& Inheritit : InheritNode) {
std::string inheritname;
c4::from_chars(Inheritit.key(), &inheritname);
std::string inheritname_constant = "JOB_" + inheritname;
/*
if (!script_get_constant(inheritname_constant.c_str(), &constant)) {
this->invalidWarning(InheritNode[Inheritit.key()], "Invalid job %s.\n", inheritname.c_str());
return 0;
}
*/
uint16 inherit_job = convert_jobid_to_number(inheritname_constant.c_str());
bool active;
if (!this->asBool(InheritNode, inheritname, active))
return 0;
//uint16 inherit_job = static_cast<uint16>(inherited_job);
if (!active) {
if (exists)
util::vector_erase_if_exists(tree->inherit_job, inherit_job);
}
else {
if (!util::vector_exists(tree->inherit_job, inherit_job))
tree->inherit_job.push_back(inherit_job);
}
}
}
if (this->nodeExists(node, "Tree")) {
for (const auto& it : node["Tree"]) {
std::string skill_name;
if (!this->asString(it, "Name", skill_name))
return 0;
//uint16 skill_id = skill_name2id(skill_name.c_str());
uint16 skill_id = convert_skill_str_to_id(skill_name.c_str());
if (skill_id == 0) {
this->invalidWarning(it["Name"], "Invalid skill name \"%s\".\n", skill_name.c_str());
return 0;
}
/*
if (!skill_get_index(skill_id)) {
this->invalidWarning(it["Name"], "Unable to load skill %s into job %hu's tree.\n", skill_name.c_str(), job_id);
return 0;
}
*/
std::shared_ptr<bot_skill_tree_entry> entry;
bool skill_exists = tree->skills.count(skill_id) > 0;
if (skill_exists)
entry = tree->skills[skill_id];
else
entry = std::make_shared<bot_skill_tree_entry>();
entry->skill_id = skill_id;
uint16 max_lv;
if (!this->asUInt16(it, "MaxLevel", max_lv))
return 0;
if (max_lv > MAX_SKILL_LEVEL) {
this->invalidWarning(it["MaxLevel"], "MaxLevel exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL);
return 0;
}
/*
uint16 skill_lv_max = skill_get_max(skill_id);
if (max_lv > skill_lv_max) {
this->invalidWarning(it["MaxLevel"], "Skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), max_lv, skill_lv_max);
max_lv = skill_lv_max;
}
*/
// if (max_lv == 0) { // skill lvl 0 removed on loadingFinished (because of inherit)
// if (!skill_exists || entry->skill_id.erase(skill_id) == 0)
// this->invalidWarning(it["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name.c_str(), job_name.c_str());
// continue;
// }
entry->max_lv = max_lv;
if (this->nodeExists(it, "BaseLevel")) {
uint32 baselv;
if (!this->asUInt32(it, "BaseLevel", baselv))
return 0;
/*
uint32 baselv_max = bot_job_db.get_maxBaseLv(job_id);
if (baselv > baselv_max) {
this->invalidWarning(it["BaseLevel"], "Skill %hu's base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n",
skill_id, baselv, job_name.c_str(), baselv_max);
baselv = baselv_max;
}
*/
entry->baselv = baselv;
}
else {
if (!skill_exists)
entry->baselv = 0;
}
if (this->nodeExists(it, "JobLevel")) {
uint32 joblv;
if (!this->asUInt32(it, "JobLevel", joblv))
return 0;
/*
uint32 joblv_max = bot_job_db.get_maxJobLv(job_id);
if (joblv > joblv_max) {
this->invalidWarning(it["JobLevel"], "Skill %hu's job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n",
skill_id, joblv, job_name.c_str(), joblv_max);
joblv = joblv_max;
}
*/
entry->joblv = joblv;
}
else {
if (!skill_exists)
entry->joblv = 0;
}
if (this->nodeExists(it, "Requires")) {
for (const auto& Requiresit : it["Requires"]) {
if (!this->nodesExist(Requiresit, { "Name" }))
return 0;
std::string skill_name_req;
if (!this->asString(Requiresit, "Name", skill_name_req))
return 0;
//uint16 skill_id_req = skill_name2id(skill_name_req.c_str());
uint16 skill_id_req = convert_skill_str_to_id(skill_name_req.c_str());
if (skill_id_req == 0) {
this->invalidWarning(Requiresit["Name"], "Invalid skill name \"%s\".\n", skill_name_req.c_str());
return 0;
}
uint16 lv_req;
if (!this->asUInt16(Requiresit, "Level", lv_req))
return 0;
if (lv_req > MAX_SKILL_LEVEL) {
this->invalidWarning(Requiresit["Level"], "Level exceeds the maximum skill level of %d, skipping.\n", MAX_SKILL_LEVEL);
return 0;
}
/*
uint16 lv_req_max = skill_get_max(skill_id_req);
if (lv_req > lv_req_max) {
this->invalidWarning(it["MaxLevel"], "Required skill %s's level %hu exceeds the skill's max level %hu. Capping skill level.\n", skill_name.c_str(), lv_req, lv_req_max);
lv_req = lv_req_max;
}
*/
if (lv_req == 0) {
if (entry->need.erase(skill_id_req) == 0)
this->invalidWarning(Requiresit["Name"], "Failed to erase %s, the skill doesn't exist in for job %s, skipping.\n", skill_name_req.c_str(), job_name.c_str());
continue;
}
entry->need[skill_id_req] = lv_req;
}
}
if (this->nodeExists(it, "Exclude")) {
bool exclude;
if (!this->asBool(it, "Exclude", exclude))
return 0;
entry->exclude_inherit = exclude;
}
else {
if (!skill_exists)
entry->exclude_inherit = false;
}
if (!skill_exists)
tree->skills.insert({ skill_id, entry });
}
}
if (!exists)
this->put(job_id, tree);
return true;
}
/*
* Checks if the required skills to a skill are met
*/
bool check_skill_requirements(int job_id, int skill_id, bot_skill_bundle& skill_bundle) {
if (is_skill_validation_exception(job_id, skill_id)) {
// Skill is exception for this job -> is not validated
std::string skill_name = convert_skill_id_to_str(skill_id);
ShowNotice("Skill %s is marked as exception to this job (%d)\n",skill_name.c_str(), job_id);
return true;
}
std::shared_ptr<bot_skill_tree> tree = bot_skill_tree_db.find(job_id);
if (!tree) {
ShowError("Skill ID %d (%s) not found in bot_skill_tree_db\n", skill_id, convert_skill_id_to_str(skill_id));
return false;
}
// Check the size of skills before iterating
if (tree->skills.empty()) {
ShowError("Skill tree for ID %d is empty\n", skill_id);
return false;
}
for (const auto& skill : tree->skills) {
//ShowDebug("Skill Found in tree: %s \n", convert_skill_id_to_str(skill.first));
if (skill.first == skill_id) {
//ShowDebug("skill max lvl : %d \n", skill.second->max_lv);
bool requirements_met = true;
for (const auto needed_skill : skill.second->need) {
//ShowDebug("requires skill %s on level %d\n", convert_skill_id_to_str(needed_skill.first), needed_skill.second);
bool skill_found = false;
for (const auto bundled_skill : skill_bundle.skills) {
if (needed_skill.first == bundled_skill.skill_id && needed_skill.second <= bundled_skill.level) {
// Skill must be in bundle and at least smaller or same level as required
skill_found = true;
break;
}
}
if (!skill_found) {
requirements_met = false;
break;
}
}
if (requirements_met) {
ShowDebug("All skill requirements fullfilled");
return true; //no missing skills, requirements are met
}
}
}
ShowDebug("One or more skill requirements not fullfilled");
return false; //Skill_bundle misses at least a required skill
}
void init_bot_skill_tree_db() {
bot_skill_tree_db.reload();
}
void BotSkillTreeDatabase::loadingFinished() {
std::unordered_map<uint16, std::shared_ptr<bot_skill_tree>> job_tree; // get the data from skill_tree_db before populate it
for (auto& data : *this) {
if (data.second->inherit_job.empty())
continue;
std::shared_ptr<bot_skill_tree> skill_tree = std::make_shared<bot_skill_tree>();
/*
uint32 baselv_max = bot_job_db.get_maxBaseLv(data.first);
uint32 joblv_max = bot_job_db.get_maxJobLv(data.first);
*/
for (const auto& inherit_job : data.second->inherit_job) {
std::shared_ptr<bot_skill_tree> tree = this->find(inherit_job);
if (tree == nullptr || tree->skills.empty())
continue;
for (const auto& it : tree->skills) {
if (it.second->exclude_inherit)
continue;
if (data.second->skills.count(it.first) > 0) // skill already in the skill tree
continue;
if (skill_tree->skills.count(it.first) > 0) // replaced by the last inheritance
skill_tree->skills[it.first] = it.second;
else
skill_tree->skills.insert({ it.first, it.second });
std::shared_ptr<bot_skill_tree_entry> skill = skill_tree->skills[it.first];
/*
if (skill->baselv > baselv_max) {
ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s base level requirement %hu exceeds job %s's max base level %d. Capping skill base level.\n",
skill_get_name(skill->skill_id), skill->skill_id, skill->baselv, job_name(data.first), baselv_max);
skill->baselv = baselv_max;
}
if (skill->joblv > joblv_max) {
ShowWarning("SkillTreeDatabase: Skill %s (%hu)'s job level requirement %hu exceeds job %s's max job level %d. Capping skill job level.\n",
skill_get_name(skill->skill_id), skill->skill_id, skill->joblv, job_name(data.first), joblv_max);
skill->joblv = joblv_max;
}
*/
}
}
if (skill_tree != nullptr && !skill_tree->skills.empty())
job_tree.insert({ data.first, skill_tree });
}
if (!job_tree.empty()) {
for (auto& data : *this) {
if (job_tree.count(data.first) == 0)
continue;
data.second->skills.insert(job_tree[data.first]->skills.begin(), job_tree[data.first]->skills.end());
}
}
// remove skills with max_lv = 0
for (const auto& job : *this) {
if (job.second->skills.empty())
continue;
auto it = job.second->skills.begin();
while (it != job.second->skills.end()) {
if (it->second->max_lv == 0) {
it = job.second->skills.erase(it);
}
else {
it++;
}
}
}
TypesafeYamlDatabase::loadingFinished();
}
std::unordered_map<uint16, uint16> parse_skill_bundle(const nlohmann::json& skill_bundle) {
std::unordered_map<uint16, uint16> parsed_skill_bundle;
for (const auto& skill : skill_bundle.items()) {
uint16 skill_id = convert_skill_str_to_id(skill.key().c_str());
if (skill_id < 0) {
ShowError("Invalid skill ID for skill string: %s\n", skill.key().c_str());
continue; // Skip invalid skills
}
uint16 level = skill.value(); // Assuming the value represents the skill level
parsed_skill_bundle[skill_id] = level;
}
return parsed_skill_bundle;
}
bool validate_skills(uint16 start_job, bot_skill_bundle& skill_bundle) {
for (const auto& skill : skill_bundle.skills) {
int skill_id = skill.skill_id;
uint16 level = skill.level;
// Check requirements
if (!check_skill_requirements(start_job, skill_id, skill_bundle)) {
std::string skill_name = convert_skill_id_to_str(skill_id);
ShowError("Skill %s(%d) at level %d does not meet requirements\n", skill_name.c_str(),skill_id, level);
return false; // Requirements not met
}
}
return true;
}
void prepare_skill_bundle(bot_skill_bundle& bundle, uint32 char_id, const nlohmann::json& character) {
//Select for the used profile
std::string skill_profile = character["char_skills_profile"];
if (skill_profile == "CUSTOM") {
if (!character.contains("skills")) {
throw std::runtime_error("skills data is missing for CUSTOM skills profile.");
}
const auto& skills = character["skills"];
//Initialize the skill bundle
bundle.skills.clear();
// Iterate over the skills in the JSON
for (const auto& skill : skills.items()) {
std::string skill_name = skill.key();
int skill_level = skill.value();
// Convert skill name to skill ID
int skill_id = convert_skill_str_to_id(skill_name.c_str());
if (skill_id < 0) { // Invalid skill ID
ShowWarning("Skill ID %s is Invalid. Skipping...\n", skill_name.c_str());
continue; // Skip invalid skills
}
std::string job_str = character["start_job"];
uint16 related_job = convert_jobid_to_number(job_str.c_str());
validate_skills(related_job, bundle);
// Add the skill to the skill bundle
bot_skill new_skill{};
new_skill.char_id = char_id; // the character id the skill belongs to
new_skill.skill_id = static_cast<uint16>(skill_id);
new_skill.level = static_cast<uint16>(skill_level);
new_skill.flag = 0; // Default: 0 for permanent
bundle.skills.push_back(new_skill);
}
}
else {
// Load skill Profile
std::string job_str = character["start_job"];
int job_id = convert_jobid_to_number(job_str.c_str());
std::string job_profile_file_path = "conf/bot_profiles/skill_profiles/" + job_str + "_skills_profile.json";
nlohmann::json json_skill_profiles;
// Try to Open skill Profile
std::ifstream skill_profile_file(job_profile_file_path);
if (!skill_profile_file.is_open()) {
ShowWarning("Failed to open skill_profile_file %s. Trying to load DEFAULT instead.", job_profile_file_path.c_str());
job_profile_file_path = "conf/bot_profiles/skill_profiles/DEFAULT_skills_profile.json";
skill_profile_file.open(job_profile_file_path);
if (!skill_profile_file.is_open()) {
throw std::runtime_error("Failed to open both job and default profiles.");
}
}
// Parse Profile Data
try {
skill_profile_file >> json_skill_profiles;
}
catch (const nlohmann::json::parse_error& e) {
throw std::runtime_error("Error parsing build profile JSON: " + std::string(e.what()));
}
if (!json_skill_profiles.contains(skill_profile)) {
// If build profile is not found, default to INIT
skill_profile = "INIT";
}
const auto& profile = json_skill_profiles[skill_profile];
//Initialize the skill bundle
bundle.skills.clear();
// Iterate over the skills in the JSON
for (const auto& skill : profile.items()) {
std::string skill_name = skill.key();
int skill_level = skill.value();
// Convert skill name to skill ID
int skill_id = convert_skill_str_to_id(skill_name.c_str());
if (skill_id < 0) { // Invalid skill ID
ShowWarning("Skill ID %s is Invalid. Skipping...\n", skill_name.c_str());
continue; // Skip invalid skills
}
std::string job_str = character["start_job"];
uint16 related_job = convert_jobid_to_number(job_str.c_str());
validate_skills(related_job, bundle);
// Add the skill to the skill bundle
bot_skill new_skill{};
new_skill.char_id = char_id; // the character id the skill belongs to
new_skill.skill_id = static_cast<uint16>(skill_id);
new_skill.level = static_cast<uint16>(skill_level);
new_skill.flag = 0; // Default: 0 for permanent
bundle.skills.push_back(new_skill);
}
}
}
bool is_skill_validation_exception(int job_id, int skill_id) {
// Usually there should not be any exceptions for validation but in case you want to setup bots that have permanent soullink skills active or other temporaö skills, they should be added here
// A Exception is only needed if a skill should be added that is not present in the base skill tree but would use a slot in the jobs skill window like custom/item skills or soullink skills
// Map job IDs to arrays of exception skill IDs
static const std::unordered_map<int, std::vector<int>> skill_map = {
{JOB_DANCER, {BA_WHISTLE, BA_ASSASSINCROSS, BA_POEMBRAGI, BA_APPLEIDUN}}, // Dancer exceptions
{JOB_KNIGHT, {KN_ONEHAND}}, // Knight exceptions
// Add more job-specific exceptions here
};
// Check if the job_id exists in the map
auto it = skill_map.find(job_id);
if (it != skill_map.end()) {
// Check if the skill_id exists in the exception list for the job_id
const auto& exception_skills = it->second;
if (std::find(exception_skills.begin(), exception_skills.end(), skill_id) != exception_skills.end()) {
return true;
}
}
// If no match is found
return false;
}
/**********+ /**********+
* Below this point there are only large converter functions. * Below this point there are only large converter functions.
************/ ************/

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save