|
@@ -25,11 +25,9 @@
|
|
|
#include "pc.hpp"
|
|
|
#include "trade.hpp"
|
|
|
|
|
|
-struct s_homunculus_db homunculus_db[MAX_HOMUNCULUS_CLASS]; //[orn]
|
|
|
-struct homun_skill_tree_entry hskill_tree[MAX_HOMUNCULUS_CLASS][MAX_HOM_SKILL_TREE];
|
|
|
+using namespace rathena;
|
|
|
|
|
|
static TIMER_FUNC(hom_hungry);
|
|
|
-static uint16 homunculus_count;
|
|
|
|
|
|
//For holding the view data of npc classes. [Skotlex]
|
|
|
static struct view_data hom_viewdb[MAX_HOMUNCULUS_CLASS];
|
|
@@ -123,17 +121,6 @@ short hom_skill_get_index(uint16 skill_id) {
|
|
|
return skill_id;
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
-* Check homunculus class for array look up
|
|
|
-* @param class_
|
|
|
-* @return Class index or -1 if invalid class
|
|
|
-*/
|
|
|
-static short hom_class2index(int class_) {
|
|
|
- if (homdb_checkid(class_))
|
|
|
- return class_ - HM_CLASS_BASE;
|
|
|
- return -1;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* Get homunculus view data
|
|
|
* @param class_ Homunculus class
|
|
@@ -343,77 +330,70 @@ int hom_delete(struct homun_data *hd, int emote)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
-* Calculates homunculus skill tree
|
|
|
-* @param hd
|
|
|
-* @param flag_envolve
|
|
|
-*/
|
|
|
-void hom_calc_skilltree(struct homun_data *hd, bool flag_evolve) {
|
|
|
- uint8 i;
|
|
|
- short c = 0;
|
|
|
-
|
|
|
- nullpo_retv(hd);
|
|
|
-
|
|
|
- /* load previous homunculus form skills first. */
|
|
|
- if (hd->homunculus.prev_class != 0 && (c = hom_class2index(hd->homunculus.prev_class)) >= 0) {
|
|
|
- for (i = 0; i < MAX_HOM_SKILL_TREE; i++) {
|
|
|
- uint16 skill_id;
|
|
|
- short idx = -1;
|
|
|
- bool fail = false;
|
|
|
- if (!(skill_id = hskill_tree[c][i].id) || (idx = hom_skill_get_index(skill_id)) == -1)
|
|
|
- continue;
|
|
|
- if (hd->homunculus.hskill[idx].id)
|
|
|
- continue; //Skill already known.
|
|
|
- if (!battle_config.skillfree) {
|
|
|
- uint8 j;
|
|
|
- if (hskill_tree[c][i].need_level > hd->homunculus.level)
|
|
|
- continue;
|
|
|
- for (j = 0; j < MAX_HOM_SKILL_REQUIRE; j++) {
|
|
|
- if (hskill_tree[c][i].need[j].id &&
|
|
|
- hom_checkskill(hd,hskill_tree[c][i].need[j].id) < hskill_tree[c][i].need[j].lv)
|
|
|
- {
|
|
|
- fail = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (!fail)
|
|
|
- hd->homunculus.hskill[idx].id = skill_id;
|
|
|
- }
|
|
|
- }
|
|
|
+ * Calculates homunculus skill tree for specific evolve/class.
|
|
|
+ * @param hd: Homunculus data
|
|
|
+ * @param skill_tree: Homunculus db skill tree
|
|
|
+ */
|
|
|
+void hom_calc_skilltree_sub(homun_data &hd, std::vector<s_homun_skill_tree_entry> &skill_tree) {
|
|
|
+ bool evolved = false;
|
|
|
|
|
|
+ if (hd.homunculus.class_ == hd.homunculusDB->evo_class)
|
|
|
+ evolved = true;
|
|
|
|
|
|
- if ((c = hom_class2index(hd->homunculus.class_)) < 0)
|
|
|
- return;
|
|
|
+ for (const auto &skit : skill_tree) {
|
|
|
+ uint16 skill_id = skit.id;
|
|
|
+ short idx = hom_skill_get_index(skill_id);
|
|
|
|
|
|
- for (i = 0; i < MAX_HOM_SKILL_TREE; i++) {
|
|
|
- unsigned int intimacy = 0;
|
|
|
- uint16 skill_id;
|
|
|
- short idx = -1;
|
|
|
- bool fail = false;
|
|
|
- if (!(skill_id = hskill_tree[c][i].id) || (idx = hom_skill_get_index(skill_id)) == -1)
|
|
|
+ if (skill_id == 0 || idx == -1)
|
|
|
continue;
|
|
|
- if (hd->homunculus.hskill[idx].id)
|
|
|
+ if (hd.homunculus.hskill[idx].id)
|
|
|
continue; //Skill already known.
|
|
|
- intimacy = (flag_evolve) ? 10 : hd->homunculus.intimacy;
|
|
|
- if (intimacy < hskill_tree[c][i].intimacy * 100)
|
|
|
- continue;
|
|
|
+
|
|
|
+ bool fail = false;
|
|
|
+
|
|
|
if (!battle_config.skillfree) {
|
|
|
- uint8 j;
|
|
|
- if (hskill_tree[c][i].need_level > hd->homunculus.level)
|
|
|
+ if (skit.intimacy > 0 && hd.homunculus.intimacy < skit.intimacy) {
|
|
|
continue;
|
|
|
- for (j = 0; j < MAX_HOM_SKILL_REQUIRE; j++) {
|
|
|
- if (hskill_tree[c][i].need[j].id &&
|
|
|
- hom_checkskill(hd,hskill_tree[c][i].need[j].id) < hskill_tree[c][i].need[j].lv)
|
|
|
- {
|
|
|
+ }
|
|
|
+ if (skit.evolution && !evolved) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (skit.need_level > hd.homunculus.level)
|
|
|
+ continue;
|
|
|
+ for (const auto &needit : skit.need) {
|
|
|
+ if (needit.first > 0 && hom_checkskill(&hd, needit.first) < needit.second) {
|
|
|
fail = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (!fail)
|
|
|
- hd->homunculus.hskill[idx].id = skill_id;
|
|
|
+ hd.homunculus.hskill[idx].id = skill_id;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+* Calculates homunculus skill tree
|
|
|
+* @param hd: Homunculus data
|
|
|
+*/
|
|
|
+void hom_calc_skilltree(homun_data *hd) {
|
|
|
+ nullpo_retv(hd);
|
|
|
+
|
|
|
+ std::shared_ptr<s_homunculus_db> homun_current = homunculus_db.homun_search(hd->homunculus.class_);
|
|
|
+
|
|
|
+ // If the current class can't be loaded, then for sure there's no prev_class!
|
|
|
+ if (homun_current == nullptr)
|
|
|
+ return;
|
|
|
+
|
|
|
+ std::shared_ptr<s_homunculus_db> homun = homunculus_db.homun_search(hd->homunculus.prev_class);
|
|
|
+
|
|
|
+ /* load previous homunculus form skills first. */
|
|
|
+ if (homun != nullptr) {
|
|
|
+ hom_calc_skilltree_sub(*hd, homun->skill_tree);
|
|
|
}
|
|
|
|
|
|
+ hom_calc_skilltree_sub(*hd, homun_current->skill_tree);
|
|
|
+
|
|
|
if (hd->master)
|
|
|
clif_homskillinfoblock(hd->master);
|
|
|
}
|
|
@@ -446,14 +426,17 @@ short hom_checkskill(struct homun_data *hd,uint16 skill_id)
|
|
|
* @return Skill Level
|
|
|
*/
|
|
|
int hom_skill_tree_get_max(int skill_id, int b_class){
|
|
|
- uint8 i;
|
|
|
+ std::shared_ptr<s_homunculus_db> homun = homunculus_db.homun_search(b_class);
|
|
|
|
|
|
- if ((b_class = hom_class2index(b_class)) < 0)
|
|
|
+ if (homun == nullptr)
|
|
|
return 0;
|
|
|
- ARR_FIND(0, MAX_HOM_SKILL_TREE, i, hskill_tree[b_class][i].id == skill_id);
|
|
|
- if (i < MAX_HOM_SKILL_TREE)
|
|
|
- return hskill_tree[b_class][i].max;
|
|
|
- return skill_get_max(skill_id);
|
|
|
+
|
|
|
+ for (const auto &skit : homun->skill_tree) {
|
|
|
+ if (skit.id == skill_id)
|
|
|
+ return skit.max;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -462,17 +445,18 @@ int hom_skill_tree_get_max(int skill_id, int b_class){
|
|
|
* @param skill_id Homunculus skill ID
|
|
|
* @return Level required or 0 if invalid
|
|
|
**/
|
|
|
-uint8 hom_skill_get_min_level(int class_, uint16 skill_id) {
|
|
|
- short class_idx = hom_class2index(class_), skill_idx = -1;
|
|
|
- uint8 i;
|
|
|
+uint16 hom_skill_get_min_level(int class_, uint16 skill_id) {
|
|
|
+ std::shared_ptr<s_homunculus_db> homun = homunculus_db.homun_search(class_);
|
|
|
|
|
|
- if (class_idx == -1 || (skill_idx = hom_skill_get_index(skill_id)) == -1)
|
|
|
- return 0;
|
|
|
- ARR_FIND(0, MAX_HOM_SKILL_REQUIRE, i, hskill_tree[class_idx][i].id == skill_id);
|
|
|
- if (i == MAX_HOM_SKILL_REQUIRE)
|
|
|
+ if (homun == nullptr)
|
|
|
return 0;
|
|
|
|
|
|
- return hskill_tree[class_idx][i].need_level;
|
|
|
+ for (const auto &skit : homun->skill_tree) {
|
|
|
+ if (skit.id == skill_id)
|
|
|
+ return skit.need_level;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -514,30 +498,26 @@ void hom_skillup(struct homun_data *hd, uint16 skill_id)
|
|
|
*/
|
|
|
int hom_levelup(struct homun_data *hd)
|
|
|
{
|
|
|
- struct s_homunculus *hom;
|
|
|
- struct h_stats *min = NULL, *max = NULL;
|
|
|
- int growth_str, growth_agi, growth_vit, growth_int, growth_dex, growth_luk ;
|
|
|
- int growth_max_hp, growth_max_sp ;
|
|
|
int m_class;
|
|
|
|
|
|
if ((m_class = hom_class2mapid(hd->homunculus.class_)) == -1) {
|
|
|
- ShowError("hom_levelup: Invalid class %d. \n", hd->homunculus.class_);
|
|
|
+ ShowError("hom_levelup: Invalid class %d.\n", hd->homunculus.class_);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+ struct s_hom_stats *min = nullptr, *max = nullptr;
|
|
|
+
|
|
|
/// When homunculus is homunculus S, we check to see if we need to apply previous class stats
|
|
|
if(m_class&HOM_S && hd->homunculus.level < battle_config.hom_S_growth_level) {
|
|
|
- int i;
|
|
|
- if (!hd->homunculus.prev_class) {
|
|
|
- /// We also need to be sure that the previous class exists, otherwise give it something to work with
|
|
|
- hd->homunculus.prev_class = 6001;
|
|
|
- }
|
|
|
- // Give the homunculus the level up stats database it needs
|
|
|
- i = hom_search(hd->homunculus.prev_class,HOMUNCULUS_CLASS);
|
|
|
- if (i < 0) // Nothing should go wrong here, but check anyways
|
|
|
+ std::shared_ptr<s_homunculus_db> homun_s_db = homunculus_db.homun_search(hd->homunculus.prev_class);
|
|
|
+
|
|
|
+ if (homun_s_db == nullptr) {
|
|
|
+ ShowError("hom_levelup: Failed to find database entry for %d.\n", hd->homunculus.prev_class);
|
|
|
return 0;
|
|
|
- max = &homunculus_db[i].gmax;
|
|
|
- min = &homunculus_db[i].gmin;
|
|
|
+ }
|
|
|
+
|
|
|
+ max = &homun_s_db->gmax;
|
|
|
+ min = &homun_s_db->gmin;
|
|
|
}
|
|
|
|
|
|
if (((m_class&HOM_REG) && hd->homunculus.level >= battle_config.hom_max_level)
|
|
@@ -545,27 +525,28 @@ int hom_levelup(struct homun_data *hd)
|
|
|
|| !hd->exp_next || hd->homunculus.exp < hd->exp_next)
|
|
|
return 0;
|
|
|
|
|
|
- hom = &hd->homunculus;
|
|
|
- hom->level++ ;
|
|
|
- if (!(hom->level % 3))
|
|
|
- hom->skillpts++ ; //1 skillpoint each 3 base level
|
|
|
+ s_homunculus &hom = hd->homunculus;
|
|
|
|
|
|
- hom->exp -= hd->exp_next ;
|
|
|
- hd->exp_next = homun_exp_db.get_nextexp(hom->level);
|
|
|
+ hom.level++;
|
|
|
+ if (!(hom.level % 3))
|
|
|
+ hom.skillpts++; //1 skillpoint each 3 base level
|
|
|
+
|
|
|
+ hom.exp -= hd->exp_next;
|
|
|
+ hd->exp_next = homun_exp_db.get_nextexp(hom.level);
|
|
|
|
|
|
if (!max) {
|
|
|
max = &hd->homunculusDB->gmax;
|
|
|
min = &hd->homunculusDB->gmin;
|
|
|
}
|
|
|
|
|
|
- growth_max_hp = rnd_value(min->HP, max->HP);
|
|
|
- growth_max_sp = rnd_value(min->SP, max->SP);
|
|
|
- growth_str = rnd_value(min->str, max->str);
|
|
|
- growth_agi = rnd_value(min->agi, max->agi);
|
|
|
- growth_vit = rnd_value(min->vit, max->vit);
|
|
|
- growth_dex = rnd_value(min->dex, max->dex);
|
|
|
- growth_int = rnd_value(min->int_,max->int_);
|
|
|
- growth_luk = rnd_value(min->luk, max->luk);
|
|
|
+ int growth_max_hp = rnd_value(min->HP, max->HP);
|
|
|
+ int growth_max_sp = rnd_value(min->SP, max->SP);
|
|
|
+ int growth_str = rnd_value(min->str, max->str);
|
|
|
+ int growth_agi = rnd_value(min->agi, max->agi);
|
|
|
+ int growth_vit = rnd_value(min->vit, max->vit);
|
|
|
+ int growth_dex = rnd_value(min->dex, max->dex);
|
|
|
+ int growth_int = rnd_value(min->int_,max->int_);
|
|
|
+ int growth_luk = rnd_value(min->luk, max->luk);
|
|
|
|
|
|
//Aegis discards the decimals in the stat growth values!
|
|
|
growth_str-=growth_str%10;
|
|
@@ -575,14 +556,14 @@ int hom_levelup(struct homun_data *hd)
|
|
|
growth_int-=growth_int%10;
|
|
|
growth_luk-=growth_luk%10;
|
|
|
|
|
|
- hom->max_hp += growth_max_hp;
|
|
|
- hom->max_sp += growth_max_sp;
|
|
|
- hom->str += growth_str;
|
|
|
- hom->agi += growth_agi;
|
|
|
- hom->vit += growth_vit;
|
|
|
- hom->dex += growth_dex;
|
|
|
- hom->int_+= growth_int;
|
|
|
- hom->luk += growth_luk;
|
|
|
+ hom.max_hp += growth_max_hp;
|
|
|
+ hom.max_sp += growth_max_sp;
|
|
|
+ hom.str += growth_str;
|
|
|
+ hom.agi += growth_agi;
|
|
|
+ hom.vit += growth_vit;
|
|
|
+ hom.dex += growth_dex;
|
|
|
+ hom.int_+= growth_int;
|
|
|
+ hom.luk += growth_luk;
|
|
|
|
|
|
APPLY_HOMUN_LEVEL_STATWEIGHT();
|
|
|
|
|
@@ -605,19 +586,19 @@ int hom_levelup(struct homun_data *hd)
|
|
|
|
|
|
/**
|
|
|
* Changes homunculus class
|
|
|
-* @param hd
|
|
|
-* @param class_ old class
|
|
|
-* @reutrn Fals if the class cannot be changed, True if otherwise
|
|
|
+* @param hd: Homunculus data
|
|
|
+* @param class_: New class
|
|
|
+* @reutrn Fails if the class cannot be changed, otherwise true
|
|
|
*/
|
|
|
-static bool hom_change_class(struct homun_data *hd, short class_) {
|
|
|
- int i;
|
|
|
- i = hom_search(class_,HOMUNCULUS_CLASS);
|
|
|
- if (i < 0)
|
|
|
+static bool hom_change_class(struct homun_data *hd, int32 class_) {
|
|
|
+ std::shared_ptr<s_homunculus_db> homun = homunculus_db.homun_search(class_);
|
|
|
+
|
|
|
+ if (homun == nullptr)
|
|
|
return false;
|
|
|
- hd->homunculusDB = &homunculus_db[i];
|
|
|
+
|
|
|
+ hd->homunculusDB = homun;
|
|
|
hd->homunculus.class_ = class_;
|
|
|
status_set_viewdata(&hd->bl, class_);
|
|
|
- hom_calc_skilltree(hd, 1);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
@@ -628,28 +609,28 @@ static bool hom_change_class(struct homun_data *hd, short class_) {
|
|
|
*/
|
|
|
int hom_evolution(struct homun_data *hd)
|
|
|
{
|
|
|
- struct s_homunculus *hom;
|
|
|
- struct h_stats *max, *min;
|
|
|
- map_session_data *sd;
|
|
|
nullpo_ret(hd);
|
|
|
|
|
|
if(!hd->homunculusDB->evo_class || hd->homunculus.class_ == hd->homunculusDB->evo_class) {
|
|
|
clif_emotion(&hd->bl, ET_SWEAT);
|
|
|
return 0 ;
|
|
|
}
|
|
|
- sd = hd->master;
|
|
|
+
|
|
|
+ map_session_data *sd = hd->master;
|
|
|
+
|
|
|
if (!sd)
|
|
|
return 0;
|
|
|
|
|
|
if (!hom_change_class(hd, hd->homunculusDB->evo_class)) {
|
|
|
- ShowError("hom_evolution: Can't evolve homunc from %d to %d", hd->homunculus.class_, hd->homunculusDB->evo_class);
|
|
|
+ ShowError("hom_evolution: Can't evolve homunc from %d to %d\n", hd->homunculus.class_, hd->homunculusDB->evo_class);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
//Apply evolution bonuses
|
|
|
- hom = &hd->homunculus;
|
|
|
- max = &hd->homunculusDB->emax;
|
|
|
- min = &hd->homunculusDB->emin;
|
|
|
+ s_homunculus *hom = &hd->homunculus;
|
|
|
+ s_hom_stats *max = &hd->homunculusDB->emax;
|
|
|
+ s_hom_stats *min = &hd->homunculusDB->emin;
|
|
|
+
|
|
|
hom->max_hp += rnd_value(min->HP, max->HP);
|
|
|
hom->max_sp += rnd_value(min->SP, max->SP);
|
|
|
hom->str += 10*rnd_value(min->str, max->str);
|
|
@@ -660,6 +641,8 @@ int hom_evolution(struct homun_data *hd)
|
|
|
hom->luk += 10*rnd_value(min->luk, max->luk);
|
|
|
hom->intimacy = battle_config.homunculus_evo_intimacy_reset;
|
|
|
|
|
|
+ hom_calc_skilltree(hd);
|
|
|
+
|
|
|
unit_remove_map(&hd->bl, CLR_OUTSIGHT);
|
|
|
if (map_addblock(&hd->bl))
|
|
|
return 0;
|
|
@@ -707,10 +690,12 @@ int hom_mutate(struct homun_data *hd, int homun_id)
|
|
|
prev_class = hd->homunculus.class_;
|
|
|
|
|
|
if (!hom_change_class(hd, homun_id)) {
|
|
|
- ShowError("hom_mutate: Can't evolve homunc from %d to %d", hd->homunculus.class_, homun_id);
|
|
|
+ ShowError("hom_mutate: Can't evolve homunc from %d to %d\n", hd->homunculus.class_, homun_id);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+ hom_calc_skilltree(hd);
|
|
|
+
|
|
|
unit_remove_map(&hd->bl, CLR_OUTSIGHT);
|
|
|
if(map_addblock(&hd->bl))
|
|
|
return 0;
|
|
@@ -1042,36 +1027,6 @@ void hom_change_name_ack(map_session_data *sd, char* name, int flag)
|
|
|
clif_hominfo(sd,hd,0);
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
-* Search homunculus info (food or next class)
|
|
|
-* @param key
|
|
|
-* @param type see enum e_hom_search_type
|
|
|
-* @return info found
|
|
|
-*/
|
|
|
-int hom_search(int key, int type)
|
|
|
-{
|
|
|
- int i;
|
|
|
-
|
|
|
- for (i = 0; i < homunculus_count; i++) {
|
|
|
- if (homunculus_db[i].base_class <= 0)
|
|
|
- continue;
|
|
|
- switch (type) {
|
|
|
- case HOMUNCULUS_CLASS:
|
|
|
- if (homunculus_db[i].base_class == key ||
|
|
|
- homunculus_db[i].evo_class == key)
|
|
|
- return i;
|
|
|
- break;
|
|
|
- case HOMUNCULUS_FOOD:
|
|
|
- if (homunculus_db[i].foodID == key)
|
|
|
- return i;
|
|
|
- break;
|
|
|
- default:
|
|
|
- return -1;
|
|
|
- }
|
|
|
- }
|
|
|
- return -1;
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* Create homunc structure
|
|
|
* @param sd
|
|
@@ -1079,27 +1034,28 @@ int hom_search(int key, int type)
|
|
|
*/
|
|
|
void hom_alloc(map_session_data *sd, struct s_homunculus *hom)
|
|
|
{
|
|
|
- struct homun_data *hd;
|
|
|
- int i = 0;
|
|
|
- t_tick tick = gettick();
|
|
|
-
|
|
|
nullpo_retv(sd);
|
|
|
|
|
|
Assert((sd->status.hom_id == 0 || sd->hd == 0) || sd->hd->master == sd);
|
|
|
|
|
|
- i = hom_search(hom->class_,HOMUNCULUS_CLASS);
|
|
|
- if(i < 0) {
|
|
|
+ std::shared_ptr<s_homunculus_db> homun_db = homunculus_db.homun_search(hom->class_);
|
|
|
+
|
|
|
+ if (homun_db == nullptr) {
|
|
|
ShowError("hom_alloc: unknown class [%d] for homunculus '%s', requesting deletion.\n", hom->class_, hom->name);
|
|
|
sd->status.hom_id = 0;
|
|
|
intif_homunculus_requestdelete(hom->hom_id);
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+ struct homun_data *hd;
|
|
|
+ t_tick tick = gettick();
|
|
|
+
|
|
|
sd->hd = hd = (struct homun_data*)aCalloc(1,sizeof(struct homun_data));
|
|
|
hd->bl.type = BL_HOM;
|
|
|
hd->bl.id = npc_get_new_npc_id();
|
|
|
|
|
|
hd->master = sd;
|
|
|
- hd->homunculusDB = &homunculus_db[i];
|
|
|
+ hd->homunculusDB = homun_db;
|
|
|
memcpy(&hd->homunculus, hom, sizeof(struct s_homunculus));
|
|
|
hd->exp_next = homun_exp_db.get_nextexp(hd->homunculus.level);
|
|
|
|
|
@@ -1262,35 +1218,36 @@ int hom_recv_data(uint32 account_id, struct s_homunculus *sh, int flag)
|
|
|
*/
|
|
|
bool hom_create_request(map_session_data *sd, int class_)
|
|
|
{
|
|
|
- struct s_homunculus homun;
|
|
|
- struct h_stats *base;
|
|
|
- int i;
|
|
|
-
|
|
|
nullpo_ret(sd);
|
|
|
|
|
|
- i = hom_search(class_,HOMUNCULUS_CLASS);
|
|
|
- if(i < 0)
|
|
|
+ std::shared_ptr<s_homunculus_db> homun_db = homunculus_db.homun_search(class_);
|
|
|
+
|
|
|
+ if (homun_db == nullptr)
|
|
|
return false;
|
|
|
|
|
|
+ struct s_homunculus homun;
|
|
|
+
|
|
|
memset(&homun, 0, sizeof(struct s_homunculus));
|
|
|
//Initial data
|
|
|
- safestrncpy(homun.name, homunculus_db[i].name, NAME_LENGTH-1);
|
|
|
+ safestrncpy(homun.name, homun_db->name, NAME_LENGTH-1);
|
|
|
homun.class_ = class_;
|
|
|
homun.level = 1;
|
|
|
homun.hunger = 32; //32%
|
|
|
homun.intimacy = 2100; //21/1000
|
|
|
homun.char_id = sd->status.char_id;
|
|
|
|
|
|
- homun.hp = 10 ;
|
|
|
- base = &homunculus_db[i].base;
|
|
|
- homun.max_hp = base->HP;
|
|
|
- homun.max_sp = base->SP;
|
|
|
- homun.str = base->str *10;
|
|
|
- homun.agi = base->agi *10;
|
|
|
- homun.vit = base->vit *10;
|
|
|
- homun.int_= base->int_*10;
|
|
|
- homun.dex = base->dex *10;
|
|
|
- homun.luk = base->luk *10;
|
|
|
+ homun.hp = 10;
|
|
|
+
|
|
|
+ s_hom_stats base = homun_db->base;
|
|
|
+
|
|
|
+ homun.max_hp = base.HP;
|
|
|
+ homun.max_sp = base.SP;
|
|
|
+ homun.str = base.str *10;
|
|
|
+ homun.agi = base.agi *10;
|
|
|
+ homun.vit = base.vit *10;
|
|
|
+ homun.int_= base.int_*10;
|
|
|
+ homun.dex = base.dex *10;
|
|
|
+ homun.luk = base.luk *10;
|
|
|
|
|
|
// Request homunculus creation
|
|
|
intif_homunculus_create(sd->status.account_id, &homun);
|
|
@@ -1371,12 +1328,9 @@ void hom_revive(struct homun_data *hd, unsigned int hp, unsigned int sp)
|
|
|
*/
|
|
|
void hom_reset_stats(struct homun_data *hd)
|
|
|
{ //Resets a homunc stats back to zero (but doesn't touches hunger or intimacy)
|
|
|
- struct s_homunculus_db *db;
|
|
|
- struct s_homunculus *hom;
|
|
|
- struct h_stats *base;
|
|
|
- hom = &hd->homunculus;
|
|
|
- db = hd->homunculusDB;
|
|
|
- base = &db->base;
|
|
|
+ struct s_homunculus *hom = &hd->homunculus;
|
|
|
+ struct s_hom_stats *base = &hd->homunculusDB->base;
|
|
|
+
|
|
|
hom->level = 1;
|
|
|
hom->hp = 10;
|
|
|
hom->max_hp = base->HP;
|
|
@@ -1425,7 +1379,8 @@ int hom_shuffle(struct homun_data *hd)
|
|
|
if(hd->homunculus.class_ == hd->homunculusDB->evo_class) {
|
|
|
//Evolved bonuses
|
|
|
struct s_homunculus *hom = &hd->homunculus;
|
|
|
- struct h_stats *max = &hd->homunculusDB->emax, *min = &hd->homunculusDB->emin;
|
|
|
+ struct s_hom_stats *max = &hd->homunculusDB->emax, *min = &hd->homunculusDB->emin;
|
|
|
+
|
|
|
hom->max_hp += rnd_value(min->HP, max->HP);
|
|
|
hom->max_sp += rnd_value(min->SP, max->SP);
|
|
|
hom->str += 10*rnd_value(min->str, max->str);
|
|
@@ -1483,216 +1438,583 @@ uint8 hom_get_intimacy_grade(struct homun_data *hd) {
|
|
|
return hom_intimacy_intimacy2grade(hd->homunculus.intimacy);
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
-* Read homunculus db
|
|
|
-*/
|
|
|
-static bool read_homunculusdb_sub(char* str[], int columns, int current)
|
|
|
-{
|
|
|
- int classid;
|
|
|
- uint16 i;
|
|
|
- struct s_homunculus_db *db;
|
|
|
+const std::string HomunculusDatabase::getDefaultLocation() {
|
|
|
+ return std::string(db_path) + "/homunculus_db.yml";
|
|
|
+}
|
|
|
|
|
|
- //Base Class,Evo Class
|
|
|
- classid = atoi(str[0]);
|
|
|
- if (classid < HM_CLASS_BASE || classid > HM_CLASS_MAX)
|
|
|
- {
|
|
|
- ShowError("read_homunculusdb : Invalid class %d\n", classid);
|
|
|
+bool HomunculusDatabase::parseStatusNode(const std::string &nodeName, const std::string &subNodeName, const ryml::NodeRef &node, s_hom_stats &bonus) {
|
|
|
+ uint32 value;
|
|
|
+
|
|
|
+ if (!this->asUInt32(node, nodeName, value))
|
|
|
return false;
|
|
|
- }
|
|
|
|
|
|
- //Find the ClassID, already exist or not in homunculus_db
|
|
|
- ARR_FIND(0,homunculus_count,i,homunculus_db[i].base_class == classid);
|
|
|
- if (i >= homunculus_count)
|
|
|
- db = &homunculus_db[homunculus_count];
|
|
|
- else
|
|
|
- db = &homunculus_db[i];
|
|
|
+ if (subNodeName.compare("Hp") == 0)
|
|
|
+ bonus.HP = value;
|
|
|
+ else if (subNodeName.compare("Sp") == 0)
|
|
|
+ bonus.SP = value;
|
|
|
+ else if (subNodeName.compare("Str") == 0)
|
|
|
+ bonus.str = static_cast<uint16>(value);
|
|
|
+ else if (subNodeName.compare("Agi") == 0)
|
|
|
+ bonus.agi = static_cast<uint16>(value);
|
|
|
+ else if (subNodeName.compare("Vit") == 0)
|
|
|
+ bonus.vit = static_cast<uint16>(value);
|
|
|
+ else if (subNodeName.compare("Int") == 0)
|
|
|
+ bonus.int_ = static_cast<uint16>(value);
|
|
|
+ else if (subNodeName.compare("Dex") == 0)
|
|
|
+ bonus.dex = static_cast<uint16>(value);
|
|
|
+ else if (subNodeName.compare("Luk") == 0)
|
|
|
+ bonus.luk = static_cast<uint16>(value);
|
|
|
|
|
|
- db->base_class = classid;
|
|
|
- classid = atoi(str[1]);
|
|
|
- if (classid < HM_CLASS_BASE || classid > HM_CLASS_MAX)
|
|
|
- {
|
|
|
- db->base_class = 0;
|
|
|
- ShowError("read_homunculusdb : Invalid class %d\n", classid);
|
|
|
- return false;
|
|
|
- }
|
|
|
- db->evo_class = classid;
|
|
|
- //Name, Food, Hungry Delay, Base Size, Evo Size, Race, Element, ASPD
|
|
|
- safestrncpy(db->name,str[2],NAME_LENGTH-1);
|
|
|
- db->foodID = atoi(str[3]);
|
|
|
- db->hungryDelay = atoi(str[4]);
|
|
|
- db->base_size = atoi(str[5]);
|
|
|
- db->evo_size = atoi(str[6]);
|
|
|
- db->race = atoi(str[7]);
|
|
|
- db->element = atoi(str[8]);
|
|
|
- db->baseASPD = atoi(str[9]);
|
|
|
- //base HP, SP, str, agi, vit, int, dex, luk
|
|
|
- db->base.HP = atoi(str[10]);
|
|
|
- db->base.SP = atoi(str[11]);
|
|
|
- db->base.str = atoi(str[12]);
|
|
|
- db->base.agi = atoi(str[13]);
|
|
|
- db->base.vit = atoi(str[14]);
|
|
|
- db->base.int_= atoi(str[15]);
|
|
|
- db->base.dex = atoi(str[16]);
|
|
|
- db->base.luk = atoi(str[17]);
|
|
|
- //Growth Min/Max HP, SP, str, agi, vit, int, dex, luk
|
|
|
- db->gmin.HP = atoi(str[18]);
|
|
|
- db->gmax.HP = atoi(str[19]);
|
|
|
- db->gmin.SP = atoi(str[20]);
|
|
|
- db->gmax.SP = atoi(str[21]);
|
|
|
- db->gmin.str = atoi(str[22]);
|
|
|
- db->gmax.str = atoi(str[23]);
|
|
|
- db->gmin.agi = atoi(str[24]);
|
|
|
- db->gmax.agi = atoi(str[25]);
|
|
|
- db->gmin.vit = atoi(str[26]);
|
|
|
- db->gmax.vit = atoi(str[27]);
|
|
|
- db->gmin.int_= atoi(str[28]);
|
|
|
- db->gmax.int_= atoi(str[29]);
|
|
|
- db->gmin.dex = atoi(str[30]);
|
|
|
- db->gmax.dex = atoi(str[31]);
|
|
|
- db->gmin.luk = atoi(str[32]);
|
|
|
- db->gmax.luk = atoi(str[33]);
|
|
|
- //Evolution Min/Max HP, SP, str, agi, vit, int, dex, luk
|
|
|
- db->emin.HP = atoi(str[34]);
|
|
|
- db->emax.HP = atoi(str[35]);
|
|
|
- db->emin.SP = atoi(str[36]);
|
|
|
- db->emax.SP = atoi(str[37]);
|
|
|
- db->emin.str = atoi(str[38]);
|
|
|
- db->emax.str = atoi(str[39]);
|
|
|
- db->emin.agi = atoi(str[40]);
|
|
|
- db->emax.agi = atoi(str[41]);
|
|
|
- db->emin.vit = atoi(str[42]);
|
|
|
- db->emax.vit = atoi(str[43]);
|
|
|
- db->emin.int_= atoi(str[44]);
|
|
|
- db->emax.int_= atoi(str[45]);
|
|
|
- db->emin.dex = atoi(str[46]);
|
|
|
- db->emax.dex = atoi(str[47]);
|
|
|
- db->emin.luk = atoi(str[48]);
|
|
|
- db->emax.luk = atoi(str[49]);
|
|
|
-
|
|
|
- //Check that the min/max values really are below the other one.
|
|
|
- if(db->gmin.HP > db->gmax.HP)
|
|
|
- db->gmin.HP = db->gmax.HP;
|
|
|
- if(db->gmin.SP > db->gmax.SP)
|
|
|
- db->gmin.SP = db->gmax.SP;
|
|
|
- if(db->gmin.str > db->gmax.str)
|
|
|
- db->gmin.str = db->gmax.str;
|
|
|
- if(db->gmin.agi > db->gmax.agi)
|
|
|
- db->gmin.agi = db->gmax.agi;
|
|
|
- if(db->gmin.vit > db->gmax.vit)
|
|
|
- db->gmin.vit = db->gmax.vit;
|
|
|
- if(db->gmin.int_> db->gmax.int_)
|
|
|
- db->gmin.int_= db->gmax.int_;
|
|
|
- if(db->gmin.dex > db->gmax.dex)
|
|
|
- db->gmin.dex = db->gmax.dex;
|
|
|
- if(db->gmin.luk > db->gmax.luk)
|
|
|
- db->gmin.luk = db->gmax.luk;
|
|
|
-
|
|
|
- if(db->emin.HP > db->emax.HP)
|
|
|
- db->emin.HP = db->emax.HP;
|
|
|
- if(db->emin.SP > db->emax.SP)
|
|
|
- db->emin.SP = db->emax.SP;
|
|
|
- if(db->emin.str > db->emax.str)
|
|
|
- db->emin.str = db->emax.str;
|
|
|
- if(db->emin.agi > db->emax.agi)
|
|
|
- db->emin.agi = db->emax.agi;
|
|
|
- if(db->emin.vit > db->emax.vit)
|
|
|
- db->emin.vit = db->emax.vit;
|
|
|
- if(db->emin.int_> db->emax.int_)
|
|
|
- db->emin.int_= db->emax.int_;
|
|
|
- if(db->emin.dex > db->emax.dex)
|
|
|
- db->emin.dex = db->emax.dex;
|
|
|
- if(db->emin.luk > db->emax.luk)
|
|
|
- db->emin.luk = db->emax.luk;
|
|
|
-
|
|
|
- if (i >= homunculus_count)
|
|
|
- homunculus_count++;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
-* Read homunculus db (check the files)
|
|
|
-*/
|
|
|
-void read_homunculusdb(void) {
|
|
|
- uint8 i;
|
|
|
- const char *filename[] = {
|
|
|
- DBPATH"homunculus_db.txt",
|
|
|
- DBIMPORT"/homunculus_db.txt",
|
|
|
- };
|
|
|
- homunculus_count = 0;
|
|
|
- memset(homunculus_db,0,sizeof(homunculus_db));
|
|
|
- for(i = 0; i<ARRAYLENGTH(filename); i++){
|
|
|
- sv_readdb(db_path, filename[i], ',', 50, 50, MAX_HOMUNCULUS_CLASS, &read_homunculusdb_sub, i > 0);
|
|
|
+ * Reads and parses an entry from the homunculus_db.
|
|
|
+ * @param node: YAML node containing the entry.
|
|
|
+ * @return count of successfully parsed rows
|
|
|
+ */
|
|
|
+uint64 HomunculusDatabase::parseBodyNode(const ryml::NodeRef &node) {
|
|
|
+ std::string class_name;
|
|
|
+
|
|
|
+ if (!this->asString(node, "Class", class_name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string class_name_constant = "MER_" + class_name;
|
|
|
+ int64 class_tmp;
|
|
|
+
|
|
|
+ if (!script_get_constant(class_name_constant.c_str(), &class_tmp)) {
|
|
|
+ this->invalidWarning(node["Class"], "Invalid homunculus Class \"%s\", skipping.\n", class_name.c_str());
|
|
|
+ return 0;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-/**
|
|
|
-* Read homunculus skill db
|
|
|
-* <hom class>,<skill id>,<max level>,<need level>,<req id1>,<req lv1>,<req id2>,<req lv2>,<req id3>,<req lv3>,<req id4>,<req lv4>,<req id5>,<req lv5>,<intimacy lv req>
|
|
|
-*/
|
|
|
-static bool read_homunculus_skilldb_sub(char* split[], int columns, int current) {
|
|
|
- uint16 skill_id;
|
|
|
- int8 i;
|
|
|
- short class_idx, idx = -1;
|
|
|
-
|
|
|
- // check for bounds [celest]
|
|
|
- if ((class_idx = hom_class2index(atoi(split[0]))) == -1) {
|
|
|
- ShowWarning("read_homunculus_skilldb: Invalid homunculus class %d.\n", atoi(split[0]));
|
|
|
- return false;
|
|
|
+ int32 class_id = static_cast<int32>(class_tmp);
|
|
|
+ std::shared_ptr<s_homunculus_db> hom = this->find(class_id);
|
|
|
+ bool exists = hom != nullptr;
|
|
|
+
|
|
|
+ if (!exists) {
|
|
|
+ if (!this->nodesExist(node, { "Name", "Status", "SkillTree" }))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ hom = std::make_shared<s_homunculus_db>();
|
|
|
+ hom->base_class = class_id;
|
|
|
+ hom->base = { 1 };
|
|
|
+ hom->gmin = {};
|
|
|
+ hom->gmax = {};
|
|
|
+ hom->emin = {};
|
|
|
+ hom->emax = {};
|
|
|
}
|
|
|
|
|
|
- skill_id = atoi(split[1]);
|
|
|
- if (hom_skill_get_index(skill_id) == -1) {
|
|
|
- ShowError("read_homunculus_skilldb: Invalid Homunculus skill '%s'.\n", split[1]);
|
|
|
- return false;
|
|
|
+ if (this->nodeExists(node, "Name")) {
|
|
|
+ std::string name;
|
|
|
+
|
|
|
+ if (!this->asString(node, "Name", name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ safestrncpy(hom->name, name.c_str(), sizeof(hom->name));
|
|
|
}
|
|
|
|
|
|
- // Search an empty line or a line with the same skill_id (stored in idx)
|
|
|
- ARR_FIND(0, MAX_HOM_SKILL_TREE, idx, !hskill_tree[class_idx][idx].id || hskill_tree[class_idx][idx].id == skill_id);
|
|
|
- if (idx == MAX_HOM_SKILL_TREE) {
|
|
|
- ShowWarning("Unable to load skill %d into homunculus %d's tree. Maximum number of skills per class has been reached.\n", skill_id, atoi(split[0]));
|
|
|
- return false;
|
|
|
+ if (this->nodeExists(node, "EvolutionClass")) {
|
|
|
+ std::string evo_class_name;
|
|
|
+
|
|
|
+ if (!this->asString(node, "EvolutionClass", evo_class_name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string evo_class_name_constant = "MER_" + evo_class_name;
|
|
|
+ int64 constant;
|
|
|
+
|
|
|
+ if (!script_get_constant(evo_class_name_constant.c_str(), &constant)) {
|
|
|
+ this->invalidWarning(node["EvolutionClass"], "Invalid homunculus Evolution Class %s, skipping.\n", evo_class_name.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->evo_class = static_cast<int32>(constant);
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->evo_class = class_id;
|
|
|
}
|
|
|
|
|
|
- hskill_tree[class_idx][idx].id = skill_id;
|
|
|
- hskill_tree[class_idx][idx].max = atoi(split[2]);
|
|
|
- hskill_tree[class_idx][idx].need_level = atoi(split[3]);
|
|
|
+ if (this->nodeExists(node, "Food")) {
|
|
|
+ std::string food;
|
|
|
|
|
|
- for (i = 0; i < MAX_HOM_SKILL_REQUIRE; i++) {
|
|
|
- hskill_tree[class_idx][idx].need[i].id = atoi(split[4+i*2]);
|
|
|
- hskill_tree[class_idx][idx].need[i].lv = atoi(split[4+i*2+1]);
|
|
|
+ if (!this->asString(node, "Food", food))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::shared_ptr<item_data> item = item_db.search_aegisname(food.c_str());
|
|
|
+
|
|
|
+ if (item == nullptr) {
|
|
|
+ this->invalidWarning(node["Food"], "Invalid homunculus Food %s, skipping.\n", food.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->foodID = item->nameid;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->foodID = ITEMID_PET_FOOD;
|
|
|
}
|
|
|
|
|
|
- hskill_tree[class_idx][idx].intimacy = atoi(split[14]);
|
|
|
- return true;
|
|
|
+ if (this->nodeExists(node, "HungryDelay")) {
|
|
|
+ int32 delay;
|
|
|
+
|
|
|
+ if (!this->asInt32(node, "HungryDelay", delay))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ hom->hungryDelay = delay;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->hungryDelay = 60000;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "Race")) {
|
|
|
+ std::string race;
|
|
|
+
|
|
|
+ if (!this->asString(node, "Race", race))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string race_constant = "RC_" + race;
|
|
|
+ int64 constant;
|
|
|
+
|
|
|
+ if (!script_get_constant(race_constant.c_str(), &constant)) {
|
|
|
+ this->invalidWarning(node["Race"], "Invalid homunculus Race %s, skipping.\n", race.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->race = static_cast<e_race>(constant);
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->race = RC_DEMIHUMAN;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "Element")) {
|
|
|
+ std::string element;
|
|
|
+
|
|
|
+ if (!this->asString(node, "Element", element))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string element_constant = "ELE_" + element;
|
|
|
+ int64 constant;
|
|
|
+
|
|
|
+ if (!script_get_constant(element_constant.c_str(), &constant)) {
|
|
|
+ this->invalidWarning(node["Element"], "Invalid homunculus Element %s, skipping.\n", element.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->element = static_cast<e_element>(constant);
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->element = ELE_NEUTRAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "Size")) {
|
|
|
+ std::string size;
|
|
|
+
|
|
|
+ if (!this->asString(node, "Size", size))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string size_constant = "SIZE_" + size;
|
|
|
+ int64 constant;
|
|
|
+
|
|
|
+ if (!script_get_constant(size_constant.c_str(), &constant)) {
|
|
|
+ this->invalidWarning(node["Size"], "Invalid homunculus Size %s, skipping.\n", size.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->base_size = static_cast<e_size>(constant);
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->base_size = SZ_SMALL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "EvolutionSize")) {
|
|
|
+ std::string size;
|
|
|
+
|
|
|
+ if (!this->asString(node, "EvolutionSize", size))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string size_constant = "SIZE_" + size;
|
|
|
+ int64 constant;
|
|
|
+
|
|
|
+ if (!script_get_constant(size_constant.c_str(), &constant)) {
|
|
|
+ this->invalidWarning(node["EvolutionSize"], "Invalid homunculus EvolutionSize %s, skipping.\n", size.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->base_size = static_cast<e_size>(constant);
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->base_size = SZ_MEDIUM;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "AttackDelay")) {
|
|
|
+ uint16 aspd;
|
|
|
+
|
|
|
+ if (!this->asUInt16(node, "AttackDelay", aspd))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (aspd > 2000) {
|
|
|
+ this->invalidWarning(node["AttackDelay"], "Homunculus AttackDelay %hu exceeds 2000, capping.\n", aspd);
|
|
|
+ aspd = 2000;
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->baseASPD = aspd;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ hom->baseASPD = 700;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "Status")) {
|
|
|
+ std::vector<std::string> stat_list = { "Hp", "Sp", "Str", "Agi", "Vit", "Int", "Dex", "Luk" };
|
|
|
+
|
|
|
+ for (const auto &statusNode : node["Status"]) {
|
|
|
+ if (!this->nodeExists(statusNode, "Type"))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ std::string stat_name;
|
|
|
+
|
|
|
+ if (!this->asString(statusNode, "Type", stat_name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (!util::vector_exists(stat_list, stat_name)) {
|
|
|
+ this->invalidWarning(statusNode["Type"], "Invalid Status Type %s, skipping.\n", stat_name.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(statusNode, "Base")) {
|
|
|
+ if (!this->parseStatusNode("Base", stat_name, statusNode, hom->base)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!exists) {
|
|
|
+ hom->base = { 1 };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(statusNode, "GrowthMinimum")) {
|
|
|
+ if (!this->parseStatusNode("GrowthMinimum", stat_name, statusNode, hom->gmin)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!exists) {
|
|
|
+ hom->gmin = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(statusNode, "GrowthMaximum")) {
|
|
|
+ if (!this->parseStatusNode("GrowthMaximum", stat_name, statusNode, hom->gmax)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!exists) {
|
|
|
+ hom->gmax = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(statusNode, "EvolutionMinimum")) {
|
|
|
+ if (!this->parseStatusNode("EvolutionMinimum", stat_name, statusNode, hom->emin)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!exists) {
|
|
|
+ hom->emin = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(statusNode, "EvolutionMaximum")) {
|
|
|
+ if (!this->parseStatusNode("EvolutionMaximum", stat_name, statusNode, hom->emax)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!exists) {
|
|
|
+ hom->emax = {};
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Cap values
|
|
|
+ if (hom->gmin.HP > hom->gmax.HP) {
|
|
|
+ hom->gmin.HP = hom->gmax.HP;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum HP %d is greater than GrowthMaximum HP %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.HP, hom->gmax.HP, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.SP > hom->gmax.SP) {
|
|
|
+ hom->gmin.SP = hom->gmax.SP;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum SP %d is greater than GrowthMaximum SP %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.SP, hom->gmax.SP, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.str > hom->gmax.str) {
|
|
|
+ hom->gmin.str = hom->gmax.str;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum STR %d is greater than GrowthMaximum STR %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.str, hom->gmax.str, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.agi > hom->gmax.agi) {
|
|
|
+ hom->gmin.agi = hom->gmax.agi;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum AGI %d is greater than GrowthMaximum AGI %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.agi, hom->gmax.agi, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.vit > hom->gmax.vit) {
|
|
|
+ hom->gmin.vit = hom->gmax.vit;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum VIT %d is greater than GrowthMaximum VIT %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.vit, hom->gmax.vit, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.int_ > hom->gmax.int_) {
|
|
|
+ hom->gmin.int_ = hom->gmax.int_;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum INT %d is greater than GrowthMaximum INT %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.int_, hom->gmax.int_, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.dex > hom->gmax.dex) {
|
|
|
+ hom->gmin.dex = hom->gmax.dex;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum DEX %d is greater than GrowthMaximum DEX %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.dex, hom->gmax.dex, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->gmin.luk > hom->gmax.luk) {
|
|
|
+ hom->gmin.luk = hom->gmax.luk;
|
|
|
+ this->invalidWarning(node, "GrowthMinimum LUK %d is greater than GrowthMaximum LUK %d for homunculus %s, capping minimum to maximum.\n", hom->gmin.luk, hom->gmax.luk, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.HP > hom->emax.HP) {
|
|
|
+ hom->emin.HP = hom->emax.HP;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum HP %d is greater than EvolutionMaximum HP %d for homunculus %s, capping minimum to maximum.\n", hom->emin.HP, hom->emax.HP, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.SP > hom->emax.SP) {
|
|
|
+ hom->emin.SP = hom->emax.SP;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum SP %d is greater than EvolutionMaximum SP %d for homunculus %s, capping minimum to maximum.\n", hom->emin.SP, hom->emax.SP, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.str > hom->emax.str) {
|
|
|
+ hom->emin.str = hom->emax.str;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum STR %d is greater than EvolutionMaximum STR %d for homunculus %s, capping minimum to maximum.\n", hom->emin.str, hom->emax.str, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.agi > hom->emax.agi) {
|
|
|
+ hom->emin.agi = hom->emax.agi;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum AGI %d is greater than EvolutionMaximum AGI %d for homunculus %s, capping minimum to maximum.\n", hom->emin.agi, hom->emax.agi, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.vit > hom->emax.vit) {
|
|
|
+ hom->emin.vit = hom->emax.vit;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum VIT %d is greater than EvolutionMaximum VIT %d for homunculus %s, capping minimum to maximum.\n", hom->emin.vit, hom->emax.vit, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.int_ > hom->emax.int_) {
|
|
|
+ hom->emin.int_ = hom->emax.int_;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum INT %d is greater than EvolutionMaximum INT %d for homunculus %s, capping minimum to maximum.\n", hom->emin.int_, hom->emax.int_, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.dex > hom->emax.dex) {
|
|
|
+ hom->emin.dex = hom->emax.dex;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum DEX %d is greater than EvolutionMaximum DEX %d for homunculus %s, capping minimum to maximum.\n", hom->emin.dex, hom->emax.dex, class_name.c_str());
|
|
|
+ }
|
|
|
+ if (hom->emin.luk > hom->emax.luk) {
|
|
|
+ hom->emin.luk = hom->emax.luk;
|
|
|
+ this->invalidWarning(node, "EvolutionMinimum LUK %d is greater than EvolutionMaximum LUK %d for homunculus %s, capping minimum to maximum.\n", hom->emin.luk, hom->emax.luk, class_name.c_str());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(node, "SkillTree")) {
|
|
|
+ const ryml::NodeRef &skillsNode = node["SkillTree"];
|
|
|
+
|
|
|
+ for (const ryml::NodeRef &skill : skillsNode) {
|
|
|
+ s_homun_skill_tree_entry entry;
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "Skill")) {
|
|
|
+ std::string skill_name;
|
|
|
+
|
|
|
+ if (!this->asString(skill, "Skill", skill_name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ uint16 skill_id = skill_name2id(skill_name.c_str());
|
|
|
+
|
|
|
+ if (skill_id == 0) {
|
|
|
+ this->invalidWarning(skill["Skill"], "Invalid homunculus skill %s, skipping.\n", skill_name.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!SKILL_CHK_HOMUN(skill_id)) {
|
|
|
+ this->invalidWarning(skill["Skill"], "Homunculus skill %s (%u) is out of the homunculus skill range [%u-%u], skipping.\n", skill_name.c_str(), skill_id, HM_SKILLBASE, HM_SKILLBASE + MAX_HOMUNSKILL - 1);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ entry.id = skill_id;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "Clear")) {
|
|
|
+ std::vector<s_homun_skill_tree_entry>::iterator it = hom->skill_tree.begin();
|
|
|
+ bool found = false;
|
|
|
+
|
|
|
+ while (it != hom->skill_tree.end()) {
|
|
|
+ if (it->id == entry.id) { // Skill found, remove it from the skill tree.
|
|
|
+ it = hom->skill_tree.erase(it);
|
|
|
+ found = true;
|
|
|
+ } else {
|
|
|
+ it++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!found)
|
|
|
+ this->invalidWarning(skill["Clear"], "Failed to remove nonexistent skill %s from homunuculus %s.\n", skill_db.find(entry.id)->name, class_name.c_str());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "MaxLevel")) {
|
|
|
+ uint16 level;
|
|
|
+
|
|
|
+ if (!this->asUInt16(skill, "MaxLevel", level))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ uint16 db_level = skill_get_max(entry.id);
|
|
|
+
|
|
|
+ if (level > db_level) {
|
|
|
+ this->invalidWarning(skill["MaxLevel"], "Skill %s exceeds maximum defined skill level %d from homunuculus %s, capping.\n", skill_db.find(entry.id)->name, db_level, class_name.c_str());
|
|
|
+ level = db_level;
|
|
|
+ }
|
|
|
+
|
|
|
+ entry.max = level;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "RequiredLevel")) {
|
|
|
+ uint16 level;
|
|
|
+
|
|
|
+ if (!this->asUInt16(skill, "RequiredLevel", level))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ uint16 config_max = battle_config.hom_max_level;
|
|
|
+
|
|
|
+ if ((hom_class2type(class_id) == HT_S))
|
|
|
+ config_max = battle_config.hom_S_max_level;
|
|
|
+
|
|
|
+ if (level > config_max) {
|
|
|
+ this->invalidWarning(skill["RequiredLevel"], "Homunculus Required Skill level %u exceeds maximum level %u, capping.\n", level, config_max);
|
|
|
+ level = config_max;
|
|
|
+ }
|
|
|
+
|
|
|
+ entry.need_level = level;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ entry.need_level = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "RequiredIntimacy")) {
|
|
|
+ uint16 intimacy;
|
|
|
+
|
|
|
+ if (!this->asUInt16(skill, "RequiredIntimacy", intimacy))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (intimacy > 1000) {
|
|
|
+ this->invalidWarning(skill["RequiredIntimacy"], "Homunculus Required Intimacy %u exceeds maximum intimacy 1000, capping.\n", intimacy);
|
|
|
+ intimacy = 1000;
|
|
|
+ }
|
|
|
+
|
|
|
+ entry.intimacy = intimacy * 100;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ entry.intimacy = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "RequireEvolution")) {
|
|
|
+ bool evo;
|
|
|
+
|
|
|
+ if (!this->asBool(skill, "RequireEvolution", evo))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (evo && hom->base_class == hom->evo_class) {
|
|
|
+ this->invalidWarning(skill["RequireEvolution"], "Homunculus %s does not have any evolution making skill %s unobtainable, skipping.\n", class_name.c_str(), skill_db.find(entry.id)->name);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ entry.evolution = evo;
|
|
|
+ } else {
|
|
|
+ if (!exists)
|
|
|
+ entry.evolution = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(skill, "Required")) {
|
|
|
+ const ryml::NodeRef &required = skill["Required"];
|
|
|
+
|
|
|
+ for (const ryml::NodeRef &prereqskill : required) {
|
|
|
+ uint16 skill_id = 0, skill_lv = 0;
|
|
|
+
|
|
|
+ if (this->nodeExists(prereqskill, "Skill")) {
|
|
|
+ std::string skill_name;
|
|
|
+
|
|
|
+ if (!this->asString(prereqskill, "Skill", skill_name))
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ skill_id = skill_name2id(skill_name.c_str());
|
|
|
+
|
|
|
+ if (skill_id == 0) {
|
|
|
+ this->invalidWarning(prereqskill["Skill"], "Invalid homunculus skill %s, skipping.\n", skill_name.c_str());
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!SKILL_CHK_HOMUN(skill_id)) {
|
|
|
+ this->invalidWarning(prereqskill["Skill"], "Homunculus skill %s (%u) is out of the homunculus skill range [%u-%u], skipping.\n", skill_name.c_str(), skill_id, HM_SKILLBASE, HM_SKILLBASE + MAX_HOMUNSKILL - 1);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(prereqskill, "Clear")) {
|
|
|
+ bool found = false;
|
|
|
+
|
|
|
+ for (auto &skit : hom->skill_tree) {
|
|
|
+ std::unordered_map<uint16, uint16>::iterator it = skit.need.begin();
|
|
|
+
|
|
|
+ while (it != skit.need.end()) {
|
|
|
+ if (it->first == skill_id) { // Skill found, remove it from the skill tree.
|
|
|
+ it = skit.need.erase(it);
|
|
|
+ found = true;
|
|
|
+ } else {
|
|
|
+ it++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!found)
|
|
|
+ this->invalidWarning(prereqskill["Clear"], "Failed to remove nonexistent prerequisite skill %s from homunuculus %s.\n", skill_db.find(skill_id)->name, class_name.c_str());
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->nodeExists(prereqskill, "Level")) {
|
|
|
+ if (!this->asUInt16(prereqskill, "Level", skill_lv))
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (skill_id > 0 && skill_lv > 0)
|
|
|
+ entry.need.emplace(skill_id, skill_lv);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ hom->skill_tree.push_back(entry);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!exists)
|
|
|
+ this->put(class_id, hom);
|
|
|
+
|
|
|
+ return 1;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
-* Read homunculus skill db (check the files)
|
|
|
-*/
|
|
|
-static void read_homunculus_skilldb(void) {
|
|
|
- const char *filename[] = { "homun_skill_tree.txt", DBIMPORT"/homun_skill_tree.txt"};
|
|
|
- int i;
|
|
|
- memset(hskill_tree,0,sizeof(hskill_tree));
|
|
|
- for (i = 0; i<ARRAYLENGTH(filename); i++) {
|
|
|
- sv_readdb(db_path, filename[i], ',', 15, 15, -1, &read_homunculus_skilldb_sub, i > 0);
|
|
|
+ * Since evolved homunculus share a database entry, use this search.
|
|
|
+ * !TODO: Clean this up so evolved homunculus have their own entry
|
|
|
+ * @param class_: Homun class to look up
|
|
|
+ * @return Shared pointer of homunculus on success, otherwise nullptr
|
|
|
+ */
|
|
|
+std::shared_ptr<s_homunculus_db> HomunculusDatabase::homun_search(int32 class_) {
|
|
|
+ std::shared_ptr<s_homunculus_db> hom = homunculus_db.find(class_);
|
|
|
+
|
|
|
+ if (hom != nullptr) {
|
|
|
+ return hom;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const auto &homit : homunculus_db) {
|
|
|
+ hom = homit.second;
|
|
|
+
|
|
|
+ if (hom->evo_class == class_) {
|
|
|
+ return hom;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ return nullptr;
|
|
|
}
|
|
|
|
|
|
+HomunculusDatabase homunculus_db;
|
|
|
+
|
|
|
void hom_reload(void){
|
|
|
- read_homunculusdb();
|
|
|
+ homunculus_db.load();
|
|
|
homun_exp_db.reload();
|
|
|
}
|
|
|
|
|
|
-void hom_reload_skill(void){
|
|
|
- read_homunculus_skilldb();
|
|
|
-}
|
|
|
-
|
|
|
void do_init_homunculus(void){
|
|
|
int class_;
|
|
|
|
|
|
- read_homunculusdb();
|
|
|
+ homunculus_db.load();
|
|
|
homun_exp_db.load();
|
|
|
- read_homunculus_skilldb();
|
|
|
|
|
|
// Add homunc timer function to timer func list [Toms]
|
|
|
add_timer_func_list(hom_hungry, "hom_hungry");
|