Browse Source

Converts Homunculus Database to YAML (#4659)

* Converts the Homunculus Database and Homunculus Skill Tree Database into one YAML flat file.
* General cleanups and optimizations.
* Includes CSV2YAML converter.
Thanks to @Lemongrass3110 and @Atemo!
Co-authored-by: Atemo <capucrath@gmail.com>
Aleos 2 năm trước cách đây
mục cha
commit
240f71cbce

+ 0 - 135
db/homun_skill_tree.txt

@@ -1,135 +0,0 @@
-// Homunculus Skill Tree Database
-//
-// Structure of Database:
-// Class,SkillID,MaxLv,NeedLevel,Prerequisite SkillID1,Prerequisite SkillLv1,PrereqSkillID2,PrereqSkillLv2,PrereqSkillID3,PrereqSkillLv3,PrereqSkillID4,PrereqSkillLv4,PrereqSkillID5,PrereqSkillLv5,IntimacyLvReq //SKILLNAME#Skill Name#
-//
-// 01. Class                    Homunculus ID.
-// 02. SkillID                  Skill ID of the homunuculus skill.
-// 03. MaxLv                    Maximum level of the homunuculus skill.
-// 04. NeedLevel                Homunculus level required for the skill to become available
-// 05. Prerequisite SkillID     Homunculus skill required for the skill to become available.
-// 06. Prerequisite SkillLv     Level of the required homunculus skill.
-// ...
-// 15. IntimacyLvReq			Minimum level of intimacy to unlock skill.
-//
-// NOTE: MAX_PC_SKILL_REQUIRE (typically 5) ID/Lv pairs must be specified.
-
-//Lif
-6001,8001,5,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_HEAL
-6001,8002,5,0,8001,3,0,0,0,0,0,0,0,0,0 //HLIF_AVOID
-6001,8003,5,0,8001,5,0,0,0,0,0,0,0,0,0 //HLIF_BRAIN
-//Amistr
-6002,8005,5,0,0,0,0,0,0,0,0,0,0,0,0 //HAMI_CASTLE
-6002,8006,5,0,8005,5,0,0,0,0,0,0,0,0,0 //HAMI_DEFENCE
-6002,8007,5,0,8006,3,0,0,0,0,0,0,0,0,0 //HAMI_SKIN
-//Filir
-6003,8009,5,0,0,0,0,0,0,0,0,0,0,0,0 //HFLI_MOON
-6003,8010,5,0,8009,3,0,0,0,0,0,0,0,0,0 //HFLI_FLEET
-6003,8011,5,0,8010,3,0,0,0,0,0,0,0,0,0 //HFLI_SPEED
-//Vanilmirth
-6004,8013,5,0,0,0,0,0,0,0,0,0,0,0,0 //HVAN_CAPRICE
-6004,8014,5,0,8013,3,0,0,0,0,0,0,0,0,0 //HVAN_CHAOTIC
-6004,8015,5,0,8013,5,0,0,0,0,0,0,0,0,0 //HVAN_INSTRUCT
-//Lif2
-6005,8001,5,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_HEAL
-6005,8002,5,0,8001,3,0,0,0,0,0,0,0,0,0 //HLIF_AVOID
-6005,8003,5,0,8001,5,0,0,0,0,0,0,0,0,0 //HLIF_BRAIN
-//Amistr2
-6006,8005,5,0,0,0,0,0,0,0,0,0,0,0,0 //HAMI_CASTLE
-6006,8006,5,0,8005,5,0,0,0,0,0,0,0,0,0 //HAMI_DEFENCE
-6006,8007,5,0,8006,3,0,0,0,0,0,0,0,0,0 //HAMI_SKIN
-//Filir2
-6007,8009,5,0,0,0,0,0,0,0,0,0,0,0,0 //HFLI_MOON
-6007,8010,5,0,8009,3,0,0,0,0,0,0,0,0,0 //HFLI_FLEET
-6007,8011,5,0,8010,3,0,0,0,0,0,0,0,0,0 //HFLI_SPEED
-//Vanilmirth2
-6008,8013,5,0,0,0,0,0,0,0,0,0,0,0,0 //HVAN_CAPRICE
-6008,8014,5,0,8013,3,0,0,0,0,0,0,0,0,0 //HVAN_CHAOTIC
-6008,8015,5,0,8013,5,0,0,0,0,0,0,0,0,0 //HVAN_INSTRUCT
-//Lif_H
-6009,8001,5,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_HEAL
-6009,8002,5,0,8001,3,0,0,0,0,0,0,0,0,0 //HLIF_AVOID
-6009,8003,5,0,8001,5,0,0,0,0,0,0,0,0,0 //HLIF_BRAIN
-6009,8004,3,0,0,0,0,0,0,0,0,0,0,0,910 //HLIF_CHANGE
-//Amistr_H
-6010,8005,5,0,0,0,0,0,0,0,0,0,0,0,0 //HAMI_CASTLE
-6010,8006,5,0,8005,5,0,0,0,0,0,0,0,0,0 //HAMI_DEFENCE
-6010,8007,5,0,8006,3,0,0,0,0,0,0,0,0,0 //HAMI_SKIN
-6010,8008,3,0,0,0,0,0,0,0,0,0,0,0,910 //HAMI_BLOODLUST
-//Filir_H
-6011,8009,5,0,0,0,0,0,0,0,0,0,0,0,0 //HFLI_MOON
-6011,8010,5,0,8009,3,0,0,0,0,0,0,0,0,0 //HFLI_FLEET
-6011,8011,5,0,8010,3,0,0,0,0,0,0,0,0,0 //HFLI_SPEED
-6011,8012,3,0,0,0,0,0,0,0,0,0,0,0,910 //HFLI_SBR44
-//Vanilmirth_H
-6012,8013,5,0,0,0,0,0,0,0,0,0,0,0,0 //HVAN_CAPRICE
-6012,8014,5,0,8013,3,0,0,0,0,0,0,0,0,0 //HVAN_CHAOTIC
-6012,8015,5,0,8013,5,0,0,0,0,0,0,0,0,0 //HVAN_INSTRUCT
-6012,8016,3,0,0,0,0,0,0,0,0,0,0,0,910 //HVAN_EXPLOSION
-//Lif2_H
-6013,8001,5,0,0,0,0,0,0,0,0,0,0,0,0 //HLIF_HEAL
-6013,8002,5,0,8001,3,0,0,0,0,0,0,0,0,0 //HLIF_AVOID
-6013,8003,5,0,8001,5,0,0,0,0,0,0,0,0,0 //HLIF_BRAIN
-6013,8004,3,0,0,0,0,0,0,0,0,0,0,0,910 //HLIF_CHANGE
-//Amistr2_H
-6014,8005,5,0,0,0,0,0,0,0,0,0,0,0,0 //HAMI_CASTLE
-6014,8006,5,0,8005,5,0,0,0,0,0,0,0,0,0 //HAMI_DEFENCE
-6014,8007,5,0,8006,3,0,0,0,0,0,0,0,0,0 //HAMI_SKIN
-6014,8008,3,0,0,0,0,0,0,0,0,0,0,0,910 //HAMI_BLOODLUST
-//Filir2_H
-6015,8009,5,0,0,0,0,0,0,0,0,0,0,0,0 //HFLI_MOON
-6015,8010,5,0,8009,3,0,0,0,0,0,0,0,0,0 //HFLI_FLEET
-6015,8011,5,0,8010,3,0,0,0,0,0,0,0,0,0 //HFLI_SPEED
-6015,8012,3,0,0,0,0,0,0,0,0,0,0,0,910 //HFLI_SBR44
-//Vanilmirth2_H
-6016,8013,5,0,0,0,0,0,0,0,0,0,0,0,0 //HVAN_CAPRICE
-6016,8014,5,0,8013,3,0,0,0,0,0,0,0,0,0 //HVAN_CHAOTIC
-6016,8015,5,0,8013,5,0,0,0,0,0,0,0,0,0 //HVAN_INSTRUCT
-6016,8016,3,0,0,0,0,0,0,0,0,0,0,0,910 //HVAN_EXPLOSION
-//Eira
-6048,8022,5,128,0,0,0,0,0,0,0,0,0,0,0 	//MH_LIGHT_OF_REGENE
-6048,8023,5,114,0,0,0,0,0,0,0,0,0,0,0	//MH_OVERED_BOOST
-6048,8024,10,106,0,0,0,0,0,0,0,0,0,0,0	//MH_ERASER_CUTTER
-6048,8025,10,121,0,0,0,0,0,0,0,0,0,0,0 	//MH_XENO_SLASHER
-6048,8026,5,137,0,0,0,0,0,0,0,0,0,0,0 	//MH_SILENT_BREEZE
-6048,8046,10,210,0,0,0,0,0,0,0,0,0,0,0 	//MH_CLASSY_FLUTTER
-6048,8047,10,215,0,0,0,0,0,0,0,0,0,0,0 	//MH_TWISTER_CUTTER
-6048,8048,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_ABSOLUTE_ZEPHYR
-//Bayeri
-6049,8031,10,105,0,0,0,0,0,0,0,0,0,0,0 	//MH_STAHL_HORN
-6049,8032,5,112,0,0,0,0,0,0,0,0,0,0,0 	//MH_GOLDENE_FERSE
-6049,8033,5,121,0,0,0,0,0,0,0,0,0,0,0 	//MH_STEINWAND
-6049,8034,10,138,0,0,0,0,0,0,0,0,0,0,0	//MH_HEILIGE_STANGE
-6049,8035,5,130,0,0,0,0,0,0,0,0,0,0,0	//MH_ANGRIFFS_MODUS
-6049,8055,10,210,0,0,0,0,0,0,0,0,0,0,0 	//MH_LICHT_GEHORN
-6049,8056,10,215,0,0,0,0,0,0,0,0,0,0,0 	//MH_GLANZEN_SPIES
-6049,8057,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_HEILIGE_PFERD
-6049,8058,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_GOLDENE_TONE
-//Sera
-6050,8018,5,132,0,0,0,0,0,0,0,0,0,0,0 	//MH_SUMMON_LEGION
-6050,8019,10,105,0,0,0,0,0,0,0,0,0,0,0 	//MH_NEEDLE_OF_PARALYZE
-6050,8020,5,116,0,0,0,0,0,0,0,0,0,0,0 	//MH_POISON_MIST
-6050,8021,10,123,0,0,0,0,0,0,0,0,0,0,0 	//MH_PAIN_KILLER
-6050,8052,10,210,0,0,0,0,0,0,0,0,0,0,0 	//MH_POLISHING_NEEDLE
-6050,8053,10,215,0,0,0,0,0,0,0,0,0,0,0 	//MH_TOXIN_OF_MANDARA
-6050,8054,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_NEEDLE_STINGER
-//Dieter
-6051,8039,5,122,0,0,0,0,0,0,0,0,0,0,0 	//MH_MAGMA_FLOW
-6051,8040,5,116,0,0,0,0,0,0,0,0,0,0,0 	//MH_GRANITIC_ARMOR
-6051,8041,10,109,0,0,0,0,0,0,0,0,0,0,0 	//MH_LAVA_SLIDE
-6051,8042,10,131,0,0,0,0,0,0,0,0,0,0,0 	//MH_PYROCLASTIC
-6051,8043,5,102,0,0,0,0,0,0,0,0,0,0,0 	//MH_VOLCANIC_ASH
-6051,8059,10,210,0,0,0,0,0,0,0,0,0,0,0 	//MH_BLAZING_LAVA
-6051,8044,10,215,0,0,0,0,0,0,0,0,0,0,0 	//MH_BLAST_FORGE
-6051,8045,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_TEMPERING
-//Elanor
-6052,8027,1,100,0,0,0,0,0,0,0,0,0,0,0 	//MH_STYLE_CHANGE
-6052,8028,5,100,0,0,0,0,0,0,0,0,0,0,0 	//MH_SONIC_CRAW
-6052,8029,10,114,0,0,0,0,0,0,0,0,0,0,0 	//MH_SILVERVEIN_RUSH
-6052,8030,10,128,0,0,0,0,0,0,0,0,0,0,0 	//MH_MIDNIGHT_FRENZY
-6052,8036,5,100,0,0,0,0,0,0,0,0,0,0,0 	//MH_TINDER_BREAKER
-6052,8037,5,112,0,0,0,0,0,0,0,0,0,0,0 	//MH_CBC
-6052,8038,5,133,0,0,0,0,0,0,0,0,0,0,0 	//MH_EQC
-6052,8049,10,210,0,0,0,0,0,0,0,0,0,0,0 	//MH_BRUSHUP_CLAW
-6052,8050,10,215,0,0,0,0,0,0,0,0,0,0,0 	//MH_BLAZING_AND_FURIOUS
-6052,8051,10,230,0,0,0,0,0,0,0,0,0,0,0 	//MH_THE_ONE_FIGHTER_RISES

+ 65 - 0
db/homunculus_db.yml

@@ -0,0 +1,65 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2023 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Homunculus Database
+###########################################################################
+#
+# Homunculus Settings
+#
+###########################################################################
+# - BaseClass               Base class.
+#   Name                    Name of homunculus.
+#   EvolutionClass          Evolution class.
+#   Food                    Homunculus food item. (Default: Pet_Food)
+#   HungryDelay             Time interval in milliseconds after which the hunger value is altered. (Default: 60000)
+#   Race                    Race. (Default: Demihuman)
+#   Element                 Element. (Default: Neutral)
+#   Size                    Size. (Default: Small)
+#   EvolutionSize           Evolution size. (Default: Medium)
+#   AttackDelay             Base ASPD. (Default: 700)
+#   Status:                 Homunculus stats.
+#     - Type                Type of status.
+#       Base                Base value of this status. (Default: 1)
+#       GrowthMinimum       Minimum growth of this status. (Default: 0)
+#       GrowthMaximum       Maximum growth of this status. (Default: 0)
+#       EvolutionMinimum    Minimum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#       EvolutionMaximum    Maximum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#   SkillTree:              Skill tree.
+#     - Skill               Skill name.
+#       Clear               True to remove the given skill name. (Optional)
+#       MaxLevel            Maximum level of skill.
+#       RequiredLevel       Required base level of homunculus to learn. (Default: 0)
+#       RequiredIntimacy    Required intimacy of homunculus to learn. (Default: 0)
+#       RequireEvolution    Require the homunculus to be evolved to be available. (Default: false)
+#       Required:           Prerequisite skills. (Default: null)
+#         - Skill           Prerequisite skill name.
+#           Level           Level of prerequisite skill.
+#           Clear           True to remove the given prerequisite skill name. (Optional)
+###########################################################################
+
+Header:
+  Type: HOMUNCULUS_DB
+  Version: 1
+
+Footer:
+  Imports:
+  - Path: db/pre-re/homunculus_db.yml
+    Mode: Prerenewal
+  - Path: db/re/homunculus_db.yml
+    Mode: Renewal
+  - Path: db/import/homunculus_db.yml

+ 0 - 15
db/import-tmpl/homun_skill_tree.txt

@@ -1,15 +0,0 @@
-// Homunculus Skill Tree Database
-//
-// Structure of Database:
-// Class,SkillID,MaxLv,NeedLevel,Prerequisite SkillID1,Prerequisite SkillLv1,PrereqSkillID2,PrereqSkillLv2,PrereqSkillID3,PrereqSkillLv3,PrereqSkillID4,PrereqSkillLv4,PrereqSkillID5,PrereqSkillLv5,IntimacyLvReq //SKILLNAME#Skill Name#
-//
-// 01. Class                    Homunculus ID.
-// 02. SkillID                  Skill ID of the homunuculus skill.
-// 03. MaxLv                    Maximum level of the homunuculus skill.
-// 04. NeedLevel                Homunculus level required for the skill to become available
-// 05. Prerequisite SkillID     Homunculus skill required for the skill to become available.
-// 06. Prerequisite SkillLv     Level of the required homunculus skill.
-// ...
-// 15. IntimacyLvReq			Minimum level of intimacy to unlock skill.
-//
-// NOTE: MAX_PC_SKILL_REQUIRE (typically 5) ID/Lv pairs must be specified.

+ 0 - 20
db/import-tmpl/homunculus_db.txt

@@ -1,20 +0,0 @@
-// Homunculus Database
-//
-// Structure of Database:
-// Class,EvoClass,Name,FoodID,HungryDelay,BaseSize,EvoSize,Race,Element,bASPD,bHP,bSP,bSTR,bAGI,bVIT,bINT,bDEX,bLUK,gnHP,gxHP,gnSP,gxSP,gnSTR,gxSTR,gnAGI,gxAGI,gnVIT,gxVIT,gnINT,gxINT,gnDEX,gxDEX,gnLUK,gxLUK,enHP,exHP,enSP,exSP,enSTR,exSTR,enAGI,exAGI,enVIT,exVIT,enINT,exINT,enDEX,exDEX,enLUK,exLUK
-//
-// 01. Class        Homunculus ID.
-// 02. EvoClass     Homunculus ID of the evolved version.
-// 03. Name         Name of the homunculus.
-// 04. FoodID       Item ID of the homunuclus food.
-// 05. HungryDelay  Time interval in milliseconds after which the homunculus' hunger value is altered.
-// 06. BaseSize     Size of the base homunculus class (0 = small, 1 = normal, 2 = large).
-// 07. EvoSize      Size of the evolved homunculus class (0 = small, 1 = normal, 2 = large).
-// 08. Race         Race of the homunculus (0 = formless, 1 = undead, 2 = brute, 3 = plant, 4 = insect, 5 = fish, 6 = demon, 7 = demi-human, 8 = angel, 9 = dragon).
-// 09. Element      Element of the homunculus (0 = neutral, 1 = water, 2 = earth, 3 = fire, 4 = wind, 5 = poison, 6 = holy, 7 = dark, 8 = ghost, 9 = undead).
-//                  The element level is always 1.
-// ...
-//
-// Legend: b: base, gn: growth min, gx: growth max, en: evolution min, ex: evolution max
-// NOTE: Only the growth values are in a 1/10 scale, the other stats are 1/1 (eg: 5 gmAGI means 0.5 agi)
-

+ 57 - 0
db/import-tmpl/homunculus_db.yml

@@ -0,0 +1,57 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2023 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Homunculus Database
+###########################################################################
+#
+# Homunculus Settings
+#
+###########################################################################
+# - BaseClass               Base class.
+#   Name                    Name of homunculus.
+#   EvolutionClass          Evolution class.
+#   Food                    Homunculus food item. (Default: Pet_Food)
+#   HungryDelay             Time interval in milliseconds after which the hunger value is altered. (Default: 60000)
+#   Race                    Race. (Default: Demihuman)
+#   Element                 Element. (Default: Neutral)
+#   Size                    Size. (Default: Small)
+#   EvolutionSize           Evolution size. (Default: Medium)
+#   AttackDelay             Base ASPD. (Default: 700)
+#   Status:                 Homunculus stats.
+#     - Type                Type of status.
+#       Base                Base value of this status. (Default: 1)
+#       GrowthMinimum       Minimum growth of this status. (Default: 0)
+#       GrowthMaximum       Maximum growth of this status. (Default: 0)
+#       EvolutionMinimum    Minimum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#       EvolutionMaximum    Maximum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#   SkillTree:              Skill tree.
+#     - Skill               Skill name.
+#       Clear               True to remove the given skill name. (Optional)
+#       MaxLevel            Maximum level of skill.
+#       RequiredLevel       Required base level of homunculus to learn. (Default: 0)
+#       RequiredIntimacy    Required intimacy of homunculus to learn. (Default: 0)
+#       RequireEvolution    Require the homunculus to be evolved to be available. (Default: false)
+#       Required:           Prerequisite skills. (Default: null)
+#         - Skill           Prerequisite skill name.
+#           Level           Level of prerequisite skill.
+#           Clear           True to remove the given prerequisite skill name. (Optional)
+###########################################################################
+
+Header:
+  Type: HOMUNCULUS_DB
+  Version: 1

+ 0 - 33
db/pre-re/homunculus_db.txt

@@ -1,33 +0,0 @@
-// Homunculus Database
-//
-// Structure of Database:
-// Class,EvoClass,Name,FoodID,HungryDelay,BaseSize,EvoSize,Race,Element,bASPD,bHP,bSP,bSTR,bAGI,bVIT,bINT,bDEX,bLUK,gnHP,gxHP,gnSP,gxSP,gnSTR,gxSTR,gnAGI,gxAGI,gnVIT,gxVIT,gnINT,gxINT,gnDEX,gxDEX,gnLUK,gxLUK,enHP,exHP,enSP,exSP,enSTR,exSTR,enAGI,exAGI,enVIT,exVIT,enINT,exINT,enDEX,exDEX,enLUK,exLUK
-//
-// 01. Class        Homunculus ID.
-// 02. EvoClass     Homunculus ID of the evolved version.
-// 03. Name         Name of the homunculus.
-// 04. FoodID       Item ID of the homunuclus food.
-// 05. HungryDelay  Time interval in milliseconds after which the homunculus' hunger value is altered.
-// 06. BaseSize     Size of the base homunculus class (0 = small, 1 = normal, 2 = large).
-// 07. EvoSize      Size of the evolved homunculus class (0 = small, 1 = normal, 2 = large).
-// 08. Race         Race of the homunculus (0 = formless, 1 = undead, 2 = brute, 3 = plant, 4 = insect, 5 = fish, 6 = demon, 7 = demi-human, 8 = angel, 9 = dragon).
-// 09. Element      Element of the homunculus (0 = neutral, 1 = water, 2 = earth, 3 = fire, 4 = wind, 5 = poison, 6 = holy, 7 = dark, 8 = ghost, 9 = undead).
-//                  The element level is always 1.
-// ...
-//
-// Legend: b: base, gn: growth min, gx: growth max, en: evolution min, ex: evolution max
-// NOTE: Only the growth values are in a 1/10 scale, the other stats are 1/1 (eg: 5 gmAGI means 0.5 agi)
-
-6001,6009,Lif,537,60000,0,1,7,0,700,150,40,17,20,15,35,24,12,60,100,4,9,5,19,5,19,5,19,4,20,6,20,6,20,1,10,10,20,1,5,1,4,1,5,4,10,1,10,1,3
-6002,6010,Amistr,912,60000,0,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,10,20,1,10,1,10,1,5,4,10,1,3,1,4,1,5
-6003,6011,Filir,910,60000,0,1,2,0,700,90,25,29,35,9,8,30,9,45,75,3,6,4,20,8,20,1,10,3,19,4,20,3,19,5,15,5,15,4,10,1,10,1,3,1,4,1,5,1,5
-6004,6012,Vanilmirth,911,60000,0,1,0,0,700,80,11,11,11,11,11,11,11,30,150,0,7,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,10,1,10,1,10,1,10,1,10,1,10
-6005,6013,Lif,537,60000,0,1,7,0,700,150,40,17,20,15,35,24,12,60,100,4,9,5,19,5,19,5,19,4,20,6,20,6,20,1,10,10,20,1,5,1,4,1,5,4,10,1,10,1,3
-6006,6014,Amistr,912,60000,0,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,10,20,1,10,1,10,1,5,4,10,1,3,1,4,1,5
-6007,6015,Filir,910,60000,0,1,2,0,700,90,25,29,35,9,8,30,9,45,75,3,6,4,20,8,20,1,10,3,19,4,20,3,19,5,15,5,15,4,10,1,10,1,3,1,4,1,5,1,5
-6008,6016,Vanilmirth,911,60000,0,1,0,0,700,80,11,11,11,11,11,11,11,30,150,0,7,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,10,1,10,1,10,1,10,1,10,1,10
-6048,6048,Eira,6098,60000,1,1,7,0,700,150,40,17,20,15,35,24,12,60,100,4,9,5,19,5,19,5,19,4,20,6,20,6,20,1,10,10,20,1,5,1,4,1,5,4,10,1,10,1,3
-6049,6049,Bayeri,6112,60000,1,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,10,20,1,10,1,10,1,5,4,10,1,3,1,4,1,5
-6050,6050,Sera,6108,60000,1,1,4,0,700,90,25,29,35,9,8,30,9,45,75,3,6,4,20,8,20,1,10,3,19,4,20,3,19,5,15,5,15,4,10,1,10,1,3,1,4,1,5,1,5
-6051,6051,Dieter,6104,60000,1,1,0,0,700,80,11,11,11,11,11,11,11,30,150,0,7,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,30,1,10,1,10,1,10,1,10,1,10,1,10
-6052,6052,Eleanor,6115,60000,1,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,10,20,1,10,1,10,1,5,4,10,1,3,1,4,1,5

+ 623 - 0
db/pre-re/homunculus_db.yml

@@ -0,0 +1,623 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2023 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Homunculus Database
+###########################################################################
+#
+# Homunculus Settings
+#
+###########################################################################
+# - BaseClass               Base class.
+#   Name                    Name of homunculus.
+#   EvolutionClass          Evolution class.
+#   Food                    Homunculus food item. (Default: Pet_Food)
+#   HungryDelay             Time interval in milliseconds after which the hunger value is altered. (Default: 60000)
+#   Race                    Race. (Default: Demihuman)
+#   Element                 Element. (Default: Neutral)
+#   Size                    Size. (Default: Small)
+#   EvolutionSize           Evolution size. (Default: Medium)
+#   AttackDelay             Base ASPD. (Default: 700)
+#   Status:                 Homunculus stats.
+#     - Type                Type of status.
+#       Base                Base value of this status. (Default: 1)
+#       GrowthMinimum       Minimum growth of this status. (Default: 0)
+#       GrowthMaximum       Maximum growth of this status. (Default: 0)
+#       EvolutionMinimum    Minimum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#       EvolutionMaximum    Maximum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#   SkillTree:              Skill tree.
+#     - Skill               Skill name.
+#       Clear               True to remove the given skill name. (Optional)
+#       MaxLevel            Maximum level of skill.
+#       RequiredLevel       Required base level of homunculus to learn. (Default: 0)
+#       RequiredIntimacy    Required intimacy of homunculus to learn. (Default: 0)
+#       RequireEvolution    Require the homunculus to be evolved to be available. (Default: false)
+#       Required:           Prerequisite skills. (Default: null)
+#         - Skill           Prerequisite skill name.
+#           Level           Level of prerequisite skill.
+#           Clear           True to remove the given prerequisite skill name. (Optional)
+###########################################################################
+
+Header:
+  Type: HOMUNCULUS_DB
+  Version: 1
+
+Body:
+  - Class: Lif
+    Name: Lif
+    EvolutionClass: Lif_H
+    Status:
+      - Type: Hp
+        Base: 150
+        GrowthMinimum: 60
+        GrowthMaximum: 100
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Sp
+        Base: 40
+        GrowthMinimum: 4
+        GrowthMaximum: 9
+        EvolutionMinimum: 10
+        EvolutionMaximum: 20
+      - Type: Str
+        Base: 17
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Agi
+        Base: 20
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Vit
+        Base: 15
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Int
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+    SkillTree:
+      - Skill: HLIF_HEAL
+        MaxLevel: 5
+      - Skill: HLIF_AVOID
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 3
+      - Skill: HLIF_BRAIN
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 5
+      - Skill: HLIF_CHANGE
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Amistr
+    Name: Amistr
+    EvolutionClass: Amistr_H
+    Food: Zargon
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 80
+        GrowthMaximum: 130
+        EvolutionMinimum: 10
+        EvolutionMaximum: 20
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 1
+        GrowthMaximum: 4
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+    SkillTree:
+      - Skill: HAMI_CASTLE
+        MaxLevel: 5
+      - Skill: HAMI_DEFENCE
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_CASTLE
+            Level: 5
+      - Skill: HAMI_SKIN
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_DEFENCE
+            Level: 3
+      - Skill: HAMI_BLOODLUST
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Filir
+    Name: Filir
+    EvolutionClass: Filir_H
+    Food: Garlet
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 90
+        GrowthMinimum: 45
+        GrowthMaximum: 75
+        EvolutionMinimum: 5
+        EvolutionMaximum: 15
+      - Type: Sp
+        Base: 25
+        GrowthMinimum: 3
+        GrowthMaximum: 6
+        EvolutionMinimum: 5
+        EvolutionMaximum: 15
+      - Type: Str
+        Base: 29
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 35
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Vit
+        Base: 9
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+      - Type: Int
+        Base: 8
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Dex
+        Base: 30
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Luk
+        Base: 9
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+    SkillTree:
+      - Skill: HFLI_MOON
+        MaxLevel: 5
+      - Skill: HFLI_FLEET
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_MOON
+            Level: 3
+      - Skill: HFLI_SPEED
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_FLEET
+            Level: 3
+      - Skill: HFLI_SBR44
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Vanilmirth
+    Name: Vanilmirth
+    EvolutionClass: Vanilmirth_H
+    Food: Scell
+    Race: Formless
+    Status:
+      - Type: Hp
+        Base: 80
+        GrowthMinimum: 30
+        GrowthMaximum: 150
+        EvolutionMinimum: 1
+        EvolutionMaximum: 30
+      - Type: Sp
+        Base: 11
+        GrowthMinimum: 0
+        GrowthMaximum: 7
+        EvolutionMinimum: 1
+        EvolutionMaximum: 30
+      - Type: Str
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Vit
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Dex
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Luk
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+    SkillTree:
+      - Skill: HVAN_CAPRICE
+        MaxLevel: 5
+      - Skill: HVAN_CHAOTIC
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 3
+      - Skill: HVAN_INSTRUCT
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 5
+      - Skill: HVAN_EXPLOSION
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Lif2
+    Name: Lif
+    EvolutionClass: Lif_H2
+    Status:
+      - Type: Hp
+        Base: 150
+        GrowthMinimum: 60
+        GrowthMaximum: 100
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Sp
+        Base: 40
+        GrowthMinimum: 4
+        GrowthMaximum: 9
+        EvolutionMinimum: 10
+        EvolutionMaximum: 20
+      - Type: Str
+        Base: 17
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Agi
+        Base: 20
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Vit
+        Base: 15
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Int
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+    SkillTree:
+      - Skill: HLIF_HEAL
+        MaxLevel: 5
+      - Skill: HLIF_AVOID
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 3
+      - Skill: HLIF_BRAIN
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 5
+      - Skill: HLIF_CHANGE
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Amistr2
+    Name: Amistr
+    EvolutionClass: Amistr_H2
+    Food: Zargon
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 80
+        GrowthMaximum: 130
+        EvolutionMinimum: 10
+        EvolutionMaximum: 20
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 1
+        GrowthMaximum: 4
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+    SkillTree:
+      - Skill: HAMI_CASTLE
+        MaxLevel: 5
+      - Skill: HAMI_DEFENCE
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_CASTLE
+            Level: 5
+      - Skill: HAMI_SKIN
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_DEFENCE
+            Level: 3
+      - Skill: HAMI_BLOODLUST
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Filir2
+    Name: Filir
+    EvolutionClass: Filir_H2
+    Food: Garlet
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 90
+        GrowthMinimum: 45
+        GrowthMaximum: 75
+        EvolutionMinimum: 5
+        EvolutionMaximum: 15
+      - Type: Sp
+        Base: 25
+        GrowthMinimum: 3
+        GrowthMaximum: 6
+        EvolutionMinimum: 5
+        EvolutionMaximum: 15
+      - Type: Str
+        Base: 29
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 4
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 35
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Vit
+        Base: 9
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 1
+        EvolutionMaximum: 3
+      - Type: Int
+        Base: 8
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 4
+      - Type: Dex
+        Base: 30
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+      - Type: Luk
+        Base: 9
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 1
+        EvolutionMaximum: 5
+    SkillTree:
+      - Skill: HFLI_MOON
+        MaxLevel: 5
+      - Skill: HFLI_FLEET
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_MOON
+            Level: 3
+      - Skill: HFLI_SPEED
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_FLEET
+            Level: 3
+      - Skill: HFLI_SBR44
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Vanilmirth2
+    Name: Vanilmirth
+    EvolutionClass: Vanilmirth_H2
+    Food: Scell
+    Race: Formless
+    Status:
+      - Type: Hp
+        Base: 80
+        GrowthMinimum: 30
+        GrowthMaximum: 150
+        EvolutionMinimum: 1
+        EvolutionMaximum: 30
+      - Type: Sp
+        Base: 11
+        GrowthMinimum: 0
+        GrowthMaximum: 7
+        EvolutionMinimum: 1
+        EvolutionMaximum: 30
+      - Type: Str
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Agi
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Vit
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Dex
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+      - Type: Luk
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 1
+        EvolutionMaximum: 10
+    SkillTree:
+      - Skill: HVAN_CAPRICE
+        MaxLevel: 5
+      - Skill: HVAN_CHAOTIC
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 3
+      - Skill: HVAN_INSTRUCT
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 5
+      - Skill: HVAN_EXPLOSION
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true

+ 0 - 33
db/re/homunculus_db.txt

@@ -1,33 +0,0 @@
-// Homunculus Database
-//
-// Structure of Database:
-// Class,EvoClass,Name,FoodID,HungryDelay,BaseSize,EvoSize,Race,Element,bASPD,bHP,bSP,bSTR,bAGI,bVIT,bINT,bDEX,bLUK,gnHP,gxHP,gnSP,gxSP,gnSTR,gxSTR,gnAGI,gxAGI,gnVIT,gxVIT,gnINT,gxINT,gnDEX,gxDEX,gnLUK,gxLUK,enHP,exHP,enSP,exSP,enSTR,exSTR,enAGI,exAGI,enVIT,exVIT,enINT,exINT,enDEX,exDEX,enLUK,exLUK
-//
-// 01. Class        Homunculus ID.
-// 02. EvoClass     Homunculus ID of the evolved version.
-// 03. Name         Name of the homunculus.
-// 04. FoodID       Item ID of the homunuclus food.
-// 05. HungryDelay  Time interval in milliseconds after which the homunculus' hunger value is altered.
-// 06. BaseSize     Size of the base homunculus class (0 = small, 1 = normal, 2 = large).
-// 07. EvoSize      Size of the evolved homunculus class (0 = small, 1 = normal, 2 = large).
-// 08. Race         Race of the homunculus (0 = formless, 1 = undead, 2 = brute, 3 = plant, 4 = insect, 5 = fish, 6 = demon, 7 = demi-human, 8 = angel, 9 = dragon).
-// 09. Element      Element of the homunculus (0 = neutral, 1 = water, 2 = earth, 3 = fire, 4 = wind, 5 = poison, 6 = holy, 7 = dark, 8 = ghost, 9 = undead).
-//                  The element level is always 1.
-// ...
-//
-// Legend: b: base, gn: growth min, gx: growth max, en: evolution min, ex: evolution max
-// NOTE: Only the growth values are in a 1/10 scale, the other stats are 1/1 (eg: 5 gmAGI means 0.5 agi)
-
-6001,6009,Lif,537,60000,0,1,7,0,700,150,40,17,20,15,35,24,12,60,100,4,9,5,19,5,19,5,19,4,20,6,20,6,20,800,2400,220,480,10,30,10,30,20,40,30,50,20,50,10,30
-6002,6010,Amistr,912,60000,0,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,1600,3600,120,360,20,50,10,30,20,50,20,50,10,30,10,30
-6003,6011,Filir,910,60000,0,1,2,0,700,90,25,29,35,9,8,30,9,45,75,3,6,4,20,8,20,1,10,3,19,4,20,3,19,1200,3200,200,400,20,50,10,30,20,50,20,50,10,30,10,30
-6004,6012,Vanilmirth,911,60000,0,1,0,0,700,80,11,11,11,11,11,11,11,30,150,0,7,1,30,1,30,1,30,1,30,1,30,1,30,1200,4800,480,640,10,30,10,30,10,30,20,50,10,50,10,100
-6005,6013,Lif,537,60000,0,1,7,0,700,150,40,17,20,15,35,24,12,60,100,4,9,5,19,5,19,5,19,4,20,6,20,6,20,800,2400,220,480,10,30,10,30,20,40,30,50,20,50,10,30
-6006,6014,Amistr,912,60000,0,1,2,0,700,320,10,20,17,35,11,24,12,80,130,1,4,8,20,4,20,4,20,1,10,3,19,3,19,1600,3600,120,360,20,50,10,30,20,50,20,50,10,30,10,30
-6007,6015,Filir,910,60000,0,1,2,0,700,90,25,29,35,9,8,30,9,45,75,3,6,4,20,8,20,1,10,3,19,4,20,3,19,1200,3200,200,400,20,50,10,30,20,50,20,50,10,30,10,30
-6008,6016,Vanilmirth,911,60000,0,1,0,0,700,80,11,11,11,11,11,11,11,30,150,0,7,1,30,1,30,1,30,1,30,1,30,1,30,1200,4800,480,640,10,30,10,30,10,30,20,50,10,50,10,100
-6048,6048,Eira,6098,60000,1,1,8,4,700,150,40,17,20,15,35,24,12,40,160,20,42,13,39,28,42,15,25,14,48,16,36,9,18,1000,2000,10,200,1,10,1,10,1,10,1,10,1,10,1,10
-6049,6049,Bayeri,6112,60000,1,1,2,6,700,320,10,20,17,35,11,24,12,90,360,48,52,18,36,8,36,16,32,22,44,12,24,20,36,1000,2000,10,200,1,10,1,10,1,10,1,10,1,10,1,10
-6050,6050,Sera,6108,60000,1,1,4,2,700,90,25,29,35,9,8,30,9,60,240,36,64,10,25,16,32,5,25,7,35,28,40,20,40,1000,2000,10,200,1,10,1,10,1,10,1,10,1,10,1,10
-6051,6051,Dieter,6104,60000,1,1,0,3,700,80,11,11,11,11,11,11,11,240,480,40,120,20,40,13,26,18,36,15,40,16,32,4,16,1000,2000,10,200,1,10,1,10,1,10,1,10,1,10,1,10
-6052,6052,Eleanor,6115,60000,1,1,7,5,700,320,10,20,17,35,11,24,12,60,300,10,20,20,40,10,50,24,48,5,15,12,36,2,10,1000,2000,10,200,1,10,1,10,1,10,1,10,1,10,1,10

+ 948 - 0
db/re/homunculus_db.yml

@@ -0,0 +1,948 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2023 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Homunculus Database
+###########################################################################
+#
+# Homunculus Settings
+#
+###########################################################################
+# - BaseClass               Base class.
+#   Name                    Name of homunculus.
+#   EvolutionClass          Evolution class.
+#   Food                    Homunculus food item. (Default: Pet_Food)
+#   HungryDelay             Time interval in milliseconds after which the hunger value is altered. (Default: 60000)
+#   Race                    Race. (Default: Demihuman)
+#   Element                 Element. (Default: Neutral)
+#   Size                    Size. (Default: Small)
+#   EvolutionSize           Evolution size. (Default: Medium)
+#   AttackDelay             Base ASPD. (Default: 700)
+#   Status:                 Homunculus stats.
+#     - Type                Type of status.
+#       Base                Base value of this status. (Default: 1)
+#       GrowthMinimum       Minimum growth of this status. (Default: 0)
+#       GrowthMaximum       Maximum growth of this status. (Default: 0)
+#       EvolutionMinimum    Minimum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#       EvolutionMaximum    Maximum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#   SkillTree:              Skill tree.
+#     - Skill               Skill name.
+#       Clear               True to remove the given skill name. (Optional)
+#       MaxLevel            Maximum level of skill.
+#       RequiredLevel       Required base level of homunculus to learn. (Default: 0)
+#       RequiredIntimacy    Required intimacy of homunculus to learn. (Default: 0)
+#       RequireEvolution    Require the homunculus to be evolved to be available. (Default: false)
+#       Required:           Prerequisite skills. (Default: null)
+#         - Skill           Prerequisite skill name.
+#           Level           Level of prerequisite skill.
+#           Clear           True to remove the given prerequisite skill name. (Optional)
+###########################################################################
+
+Header:
+  Type: HOMUNCULUS_DB
+  Version: 1
+
+Body:
+  - Class: Lif
+    Name: Lif
+    EvolutionClass: Lif_H
+    Status:
+      - Type: Hp
+        Base: 150
+        GrowthMinimum: 60
+        GrowthMaximum: 100
+        EvolutionMinimum: 800
+        EvolutionMaximum: 2400
+      - Type: Sp
+        Base: 40
+        GrowthMinimum: 4
+        GrowthMaximum: 9
+        EvolutionMinimum: 220
+        EvolutionMaximum: 480
+      - Type: Str
+        Base: 17
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Agi
+        Base: 20
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 15
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 20
+        EvolutionMaximum: 40
+      - Type: Int
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 30
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HLIF_HEAL
+        MaxLevel: 5
+      - Skill: HLIF_AVOID
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 3
+      - Skill: HLIF_BRAIN
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 5
+      - Skill: HLIF_CHANGE
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Amistr
+    Name: Amistr
+    EvolutionClass: Amistr_H
+    Food: Zargon
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 80
+        GrowthMaximum: 130
+        EvolutionMinimum: 1600
+        EvolutionMaximum: 3600
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 1
+        GrowthMaximum: 4
+        EvolutionMinimum: 120
+        EvolutionMaximum: 360
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HAMI_CASTLE
+        MaxLevel: 5
+      - Skill: HAMI_DEFENCE
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_CASTLE
+            Level: 5
+      - Skill: HAMI_SKIN
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_DEFENCE
+            Level: 3
+      - Skill: HAMI_BLOODLUST
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Filir
+    Name: Filir
+    EvolutionClass: Filir_H
+    Food: Garlet
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 90
+        GrowthMinimum: 45
+        GrowthMaximum: 75
+        EvolutionMinimum: 1200
+        EvolutionMaximum: 3200
+      - Type: Sp
+        Base: 25
+        GrowthMinimum: 3
+        GrowthMaximum: 6
+        EvolutionMinimum: 200
+        EvolutionMaximum: 400
+      - Type: Str
+        Base: 29
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Agi
+        Base: 35
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 9
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Int
+        Base: 8
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 30
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Luk
+        Base: 9
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HFLI_MOON
+        MaxLevel: 5
+      - Skill: HFLI_FLEET
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_MOON
+            Level: 3
+      - Skill: HFLI_SPEED
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_FLEET
+            Level: 3
+      - Skill: HFLI_SBR44
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Vanilmirth
+    Name: Vanilmirth
+    EvolutionClass: Vanilmirth_H
+    Food: Scell
+    Race: Formless
+    Status:
+      - Type: Hp
+        Base: 80
+        GrowthMinimum: 30
+        GrowthMaximum: 150
+        EvolutionMinimum: 1200
+        EvolutionMaximum: 4800
+      - Type: Sp
+        Base: 11
+        GrowthMinimum: 0
+        GrowthMaximum: 7
+        EvolutionMinimum: 480
+        EvolutionMaximum: 640
+      - Type: Str
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Agi
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 50
+      - Type: Luk
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 100
+    SkillTree:
+      - Skill: HVAN_CAPRICE
+        MaxLevel: 5
+      - Skill: HVAN_CHAOTIC
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 3
+      - Skill: HVAN_INSTRUCT
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 5
+      - Skill: HVAN_EXPLOSION
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Lif2
+    Name: Lif
+    EvolutionClass: Lif_H2
+    Status:
+      - Type: Hp
+        Base: 150
+        GrowthMinimum: 60
+        GrowthMaximum: 100
+        EvolutionMinimum: 800
+        EvolutionMaximum: 2400
+      - Type: Sp
+        Base: 40
+        GrowthMinimum: 4
+        GrowthMaximum: 9
+        EvolutionMinimum: 220
+        EvolutionMaximum: 480
+      - Type: Str
+        Base: 17
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Agi
+        Base: 20
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 15
+        GrowthMinimum: 5
+        GrowthMaximum: 19
+        EvolutionMinimum: 20
+        EvolutionMaximum: 40
+      - Type: Int
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 30
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 6
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HLIF_HEAL
+        MaxLevel: 5
+      - Skill: HLIF_AVOID
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 3
+      - Skill: HLIF_BRAIN
+        MaxLevel: 5
+        Required:
+          - Skill: HLIF_HEAL
+            Level: 5
+      - Skill: HLIF_CHANGE
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Amistr2
+    Name: Amistr
+    EvolutionClass: Amistr_H2
+    Food: Zargon
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 80
+        GrowthMaximum: 130
+        EvolutionMinimum: 1600
+        EvolutionMaximum: 3600
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 1
+        GrowthMaximum: 4
+        EvolutionMinimum: 120
+        EvolutionMaximum: 360
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HAMI_CASTLE
+        MaxLevel: 5
+      - Skill: HAMI_DEFENCE
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_CASTLE
+            Level: 5
+      - Skill: HAMI_SKIN
+        MaxLevel: 5
+        Required:
+          - Skill: HAMI_DEFENCE
+            Level: 3
+      - Skill: HAMI_BLOODLUST
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Filir2
+    Name: Filir
+    EvolutionClass: Filir_H2
+    Food: Garlet
+    Race: Brute
+    Status:
+      - Type: Hp
+        Base: 90
+        GrowthMinimum: 45
+        GrowthMaximum: 75
+        EvolutionMinimum: 1200
+        EvolutionMaximum: 3200
+      - Type: Sp
+        Base: 25
+        GrowthMinimum: 3
+        GrowthMaximum: 6
+        EvolutionMinimum: 200
+        EvolutionMaximum: 400
+      - Type: Str
+        Base: 29
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Agi
+        Base: 35
+        GrowthMinimum: 8
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 9
+        GrowthMinimum: 1
+        GrowthMaximum: 10
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Int
+        Base: 8
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 30
+        GrowthMinimum: 4
+        GrowthMaximum: 20
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Luk
+        Base: 9
+        GrowthMinimum: 3
+        GrowthMaximum: 19
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+    SkillTree:
+      - Skill: HFLI_MOON
+        MaxLevel: 5
+      - Skill: HFLI_FLEET
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_MOON
+            Level: 3
+      - Skill: HFLI_SPEED
+        MaxLevel: 5
+        Required:
+          - Skill: HFLI_FLEET
+            Level: 3
+      - Skill: HFLI_SBR44
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Vanilmirth2
+    Name: Vanilmirth
+    EvolutionClass: Vanilmirth_H2
+    Food: Scell
+    Race: Formless
+    Status:
+      - Type: Hp
+        Base: 80
+        GrowthMinimum: 30
+        GrowthMaximum: 150
+        EvolutionMinimum: 1200
+        EvolutionMaximum: 4800
+      - Type: Sp
+        Base: 11
+        GrowthMinimum: 0
+        GrowthMaximum: 7
+        EvolutionMinimum: 480
+        EvolutionMaximum: 640
+      - Type: Str
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Agi
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Vit
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 30
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 20
+        EvolutionMaximum: 50
+      - Type: Dex
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 50
+      - Type: Luk
+        Base: 11
+        GrowthMinimum: 1
+        GrowthMaximum: 30
+        EvolutionMinimum: 10
+        EvolutionMaximum: 100
+    SkillTree:
+      - Skill: HVAN_CAPRICE
+        MaxLevel: 5
+      - Skill: HVAN_CHAOTIC
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 3
+      - Skill: HVAN_INSTRUCT
+        MaxLevel: 5
+        Required:
+          - Skill: HVAN_CAPRICE
+            Level: 5
+      - Skill: HVAN_EXPLOSION
+        MaxLevel: 3
+        RequiredIntimacy: 910
+        RequireEvolution: true
+  - Class: Eira
+    Name: Eira
+    Food: Small_Snow_Flower
+    Race: Angel
+    Element: Wind
+    Size: Medium
+    Status:
+      - Type: Hp
+        Base: 150
+        GrowthMinimum: 40
+        GrowthMaximum: 160
+      - Type: Sp
+        Base: 40
+        GrowthMinimum: 20
+        GrowthMaximum: 42
+      - Type: Str
+        Base: 17
+        GrowthMinimum: 13
+        GrowthMaximum: 39
+      - Type: Agi
+        Base: 20
+        GrowthMinimum: 28
+        GrowthMaximum: 42
+      - Type: Vit
+        Base: 15
+        GrowthMinimum: 15
+        GrowthMaximum: 25
+      - Type: Int
+        Base: 35
+        GrowthMinimum: 14
+        GrowthMaximum: 48
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 16
+        GrowthMaximum: 36
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 9
+        GrowthMaximum: 18
+    SkillTree:
+      - Skill: MH_LIGHT_OF_REGENE
+        MaxLevel: 5
+        RequiredLevel: 128
+      - Skill: MH_OVERED_BOOST
+        MaxLevel: 5
+        RequiredLevel: 114
+      - Skill: MH_ERASER_CUTTER
+        MaxLevel: 10
+        RequiredLevel: 106
+      - Skill: MH_XENO_SLASHER
+        MaxLevel: 10
+        RequiredLevel: 121
+      - Skill: MH_SILENT_BREEZE
+        MaxLevel: 5
+        RequiredLevel: 137
+      - Skill: MH_CLASSY_FLUTTER
+        MaxLevel: 10
+        RequiredLevel: 210
+      - Skill: MH_TWISTER_CUTTER
+        MaxLevel: 10
+        RequiredLevel: 215
+      - Skill: MH_ABSOLUTE_ZEPHYR
+        MaxLevel: 10
+        RequiredLevel: 230
+  - Class: Bayeri
+    Name: Bayeri
+    Food: Fresh_Plant
+    Race: Brute
+    Element: Holy
+    Size: Medium
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 90
+        GrowthMaximum: 360
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 48
+        GrowthMaximum: 52
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 18
+        GrowthMaximum: 36
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 8
+        GrowthMaximum: 36
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 16
+        GrowthMaximum: 32
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 22
+        GrowthMaximum: 44
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 12
+        GrowthMaximum: 24
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 20
+        GrowthMaximum: 36
+    SkillTree:
+      - Skill: MH_STAHL_HORN
+        MaxLevel: 10
+        RequiredLevel: 105
+      - Skill: MH_GOLDENE_FERSE
+        MaxLevel: 5
+        RequiredLevel: 112
+      - Skill: MH_STEINWAND
+        MaxLevel: 5
+        RequiredLevel: 121
+      - Skill: MH_HEILIGE_STANGE
+        MaxLevel: 10
+        RequiredLevel: 138
+      - Skill: MH_ANGRIFFS_MODUS
+        MaxLevel: 5
+        RequiredLevel: 130
+      - Skill: MH_LICHT_GEHORN
+        MaxLevel: 10
+        RequiredLevel: 210
+      - Skill: MH_GLANZEN_SPIES
+        MaxLevel: 10
+        RequiredLevel: 215
+      - Skill: MH_HEILIGE_PFERD
+        MaxLevel: 10
+        RequiredLevel: 230
+      - Skill: MH_GOLDENE_TONE
+        MaxLevel: 10
+        RequiredLevel: 230
+  - Class: Sera
+    Name: Sera
+    Food: Apple_Pudding
+    Race: Insect
+    Element: Earth
+    Size: Medium
+    Status:
+      - Type: Hp
+        Base: 90
+        GrowthMinimum: 60
+        GrowthMaximum: 240
+      - Type: Sp
+        Base: 25
+        GrowthMinimum: 36
+        GrowthMaximum: 64
+      - Type: Str
+        Base: 29
+        GrowthMinimum: 10
+        GrowthMaximum: 25
+      - Type: Agi
+        Base: 35
+        GrowthMinimum: 16
+        GrowthMaximum: 32
+      - Type: Vit
+        Base: 9
+        GrowthMinimum: 5
+        GrowthMaximum: 25
+      - Type: Int
+        Base: 8
+        GrowthMinimum: 7
+        GrowthMaximum: 35
+      - Type: Dex
+        Base: 30
+        GrowthMinimum: 28
+        GrowthMaximum: 40
+      - Type: Luk
+        Base: 9
+        GrowthMinimum: 20
+        GrowthMaximum: 40
+    SkillTree:
+      - Skill: MH_SUMMON_LEGION
+        MaxLevel: 5
+        RequiredLevel: 132
+      - Skill: MH_NEEDLE_OF_PARALYZE
+        MaxLevel: 10
+        RequiredLevel: 105
+      - Skill: MH_POISON_MIST
+        MaxLevel: 5
+        RequiredLevel: 116
+      - Skill: MH_PAIN_KILLER
+        MaxLevel: 10
+        RequiredLevel: 123
+      - Skill: MH_POLISHING_NEEDLE
+        MaxLevel: 10
+        RequiredLevel: 210
+      - Skill: MH_TOXIN_OF_MANDARA
+        MaxLevel: 10
+        RequiredLevel: 215
+      - Skill: MH_NEEDLE_STINGER
+        MaxLevel: 10
+        RequiredLevel: 230
+  - Class: Dieter
+    Name: Dieter
+    Food: Big_Cell
+    Race: Formless
+    Element: Fire
+    Size: Medium
+    Status:
+      - Type: Hp
+        Base: 80
+        GrowthMinimum: 240
+        GrowthMaximum: 480
+      - Type: Sp
+        Base: 11
+        GrowthMinimum: 40
+        GrowthMaximum: 120
+      - Type: Str
+        Base: 11
+        GrowthMinimum: 20
+        GrowthMaximum: 40
+      - Type: Agi
+        Base: 11
+        GrowthMinimum: 13
+        GrowthMaximum: 26
+      - Type: Vit
+        Base: 11
+        GrowthMinimum: 18
+        GrowthMaximum: 36
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 15
+        GrowthMaximum: 40
+      - Type: Dex
+        Base: 11
+        GrowthMinimum: 16
+        GrowthMaximum: 32
+      - Type: Luk
+        Base: 11
+        GrowthMinimum: 4
+        GrowthMaximum: 16
+    SkillTree:
+      - Skill: MH_MAGMA_FLOW
+        MaxLevel: 5
+        RequiredLevel: 122
+      - Skill: MH_GRANITIC_ARMOR
+        MaxLevel: 5
+        RequiredLevel: 116
+      - Skill: MH_LAVA_SLIDE
+        MaxLevel: 10
+        RequiredLevel: 109
+      - Skill: MH_PYROCLASTIC
+        MaxLevel: 10
+        RequiredLevel: 131
+      - Skill: MH_VOLCANIC_ASH
+        MaxLevel: 5
+        RequiredLevel: 102
+      - Skill: MH_BLAST_FORGE
+        MaxLevel: 10
+        RequiredLevel: 215
+      - Skill: MH_TEMPERING
+        MaxLevel: 10
+        RequiredLevel: 230
+      - Skill: MH_BLAZING_LAVA
+        MaxLevel: 10
+        RequiredLevel: 210
+  - Class: Eleanor
+    Name: Eleanor
+    Food: Bun_
+    Element: Poison
+    Size: Medium
+    Status:
+      - Type: Hp
+        Base: 320
+        GrowthMinimum: 60
+        GrowthMaximum: 300
+      - Type: Sp
+        Base: 10
+        GrowthMinimum: 10
+        GrowthMaximum: 20
+      - Type: Str
+        Base: 20
+        GrowthMinimum: 20
+        GrowthMaximum: 40
+      - Type: Agi
+        Base: 17
+        GrowthMinimum: 10
+        GrowthMaximum: 50
+      - Type: Vit
+        Base: 35
+        GrowthMinimum: 24
+        GrowthMaximum: 48
+      - Type: Int
+        Base: 11
+        GrowthMinimum: 5
+        GrowthMaximum: 15
+      - Type: Dex
+        Base: 24
+        GrowthMinimum: 12
+        GrowthMaximum: 36
+      - Type: Luk
+        Base: 12
+        GrowthMinimum: 2
+        GrowthMaximum: 10
+    SkillTree:
+      - Skill: MH_STYLE_CHANGE
+        MaxLevel: 1
+        RequiredLevel: 100
+      - Skill: MH_SONIC_CRAW
+        MaxLevel: 5
+        RequiredLevel: 100
+      - Skill: MH_SILVERVEIN_RUSH
+        MaxLevel: 10
+        RequiredLevel: 114
+      - Skill: MH_MIDNIGHT_FRENZY
+        MaxLevel: 10
+        RequiredLevel: 128
+      - Skill: MH_TINDER_BREAKER
+        MaxLevel: 5
+        RequiredLevel: 100
+      - Skill: MH_CBC
+        MaxLevel: 5
+        RequiredLevel: 112
+      - Skill: MH_EQC
+        MaxLevel: 5
+        RequiredLevel: 133
+      - Skill: MH_BRUSHUP_CLAW
+        MaxLevel: 10
+        RequiredLevel: 210
+      - Skill: MH_BLAZING_AND_FURIOUS
+        MaxLevel: 10
+        RequiredLevel: 215
+      - Skill: MH_THE_ONE_FIGHTER_RISES
+        MaxLevel: 10
+        RequiredLevel: 230

+ 36 - 0
doc/yaml/db/homunculus_db.yml

@@ -0,0 +1,36 @@
+###########################################################################
+# Homunculus Database
+###########################################################################
+#
+# Homunculus Settings
+#
+###########################################################################
+# - BaseClass               Base class.
+#   Name                    Name of homunculus.
+#   EvolutionClass          Evolution class.
+#   Food                    Homunculus food item. (Default: Pet_Food)
+#   HungryDelay             Time interval in milliseconds after which the hunger value is altered. (Default: 60000)
+#   Race                    Race. (Default: Demihuman)
+#   Element                 Element. (Default: Neutral)
+#   Size                    Size. (Default: Small)
+#   EvolutionSize           Evolution size. (Default: Medium)
+#   AttackDelay             Base ASPD. (Default: 700)
+#   Status:                 Homunculus stats.
+#     - Type                Type of status.
+#       Base                Base value of this status. (Default: 1)
+#       GrowthMinimum       Minimum growth of this status. (Default: 0)
+#       GrowthMaximum       Maximum growth of this status. (Default: 0)
+#       EvolutionMinimum    Minimum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#       EvolutionMaximum    Maximum evolution growth of this status. Only applies for homunculus that can evolve. (Default: 0)
+#   SkillTree:              Skill tree.
+#     - Skill               Skill name.
+#       Clear               True to remove the given skill name. (Optional)
+#       MaxLevel            Maximum level of skill.
+#       RequiredLevel       Required base level of homunculus to learn. (Default: 0)
+#       RequiredIntimacy    Required intimacy of homunculus to learn. (Default: 0)
+#       RequireEvolution    Require the homunculus to be evolved to be available. (Default: false)
+#       Required:           Prerequisite skills. (Default: null)
+#         - Skill           Prerequisite skill name.
+#           Level           Level of prerequisite skill.
+#           Clear           True to remove the given prerequisite skill name. (Optional)
+###########################################################################

+ 2 - 2
src/map/atcommand.cpp

@@ -4214,7 +4214,7 @@ ACMD_FUNC(reload) {
 		clif_displaymessage(fd, msg_txt(sd,98)); // Monster database has been reloaded.
 	} else if (strstr(command, "skilldb") || strncmp(message, "skilldb", 4) == 0) {
 		skill_reload();
-		hom_reload_skill();
+		homunculus_db.reload();
 		clif_displaymessage(fd, msg_txt(sd,99)); // Skill database has been reloaded.
 	} else if (strstr(command, "atcommand") || strncmp(message, "atcommand", 4) == 0) {
 		atcommand_doload();
@@ -8119,7 +8119,7 @@ ACMD_FUNC(hominfo)
 ACMD_FUNC(homstats)
 {
 	struct homun_data *hd;
-	struct s_homunculus_db *db;
+	std::shared_ptr<s_homunculus_db> db;
 	struct s_homunculus *hom;
 	int lv, min, max, evo;
 

+ 1 - 1
src/map/clif.cpp

@@ -1864,7 +1864,7 @@ void clif_send_homdata(map_session_data *sd, int state, int param)
 	int fd = sd->fd;
 
 	if ( (state == SP_INTIMATE) && (param >= 910) && (sd->hd->homunculus.class_ == sd->hd->homunculusDB->evo_class) )
-		hom_calc_skilltree(sd->hd, 0);
+		hom_calc_skilltree(sd->hd);
 
 	WFIFOHEAD(fd, packet_len(0x230));
 	WFIFOW(fd,0)=0x230;

+ 699 - 377
src/map/homunculus.cpp

@@ -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");

+ 71 - 31
src/map/homunculus.hpp

@@ -5,18 +5,20 @@
 #define HOMUNCULUS_HPP
 
 #include <string>
+
 #include <common/cbasetypes.hpp>
 #include <common/database.hpp>
 
+#include "mob.hpp"
 #include "status.hpp" // struct status_data, struct status_change
 #include "unit.hpp" // struct unit_data
 
 #ifdef RENEWAL
 	#define	HOMUN_LEVEL_STATWEIGHT_VALUE 0
 	#define APPLY_HOMUN_LEVEL_STATWEIGHT()( \
-		hom->str_value = hom->agi_value = \
-		hom->vit_value = hom->int_value = \
-		hom->dex_value = hom->luk_value = hom->level / 10 - HOMUN_LEVEL_STATWEIGHT_VALUE \
+		hom.str_value = hom.agi_value = \
+		hom.vit_value = hom.int_value = \
+		hom.dex_value = hom.luk_value = hom.level / 10 - HOMUN_LEVEL_STATWEIGHT_VALUE \
 		)
 #else
 	#define APPLY_HOMUN_LEVEL_STATWEIGHT()
@@ -40,23 +42,33 @@ public:
 	t_exp get_nextexp(uint16 level);
 };
 
-struct h_stats {
+struct s_hom_stats {
 	unsigned int HP, SP;
 	unsigned short str, agi, vit, int_, dex, luk;
 };
 
+/// Homunculus skill entry [Celest]
+struct s_homun_skill_tree_entry {
+	uint16 id;			///< Skill ID
+	uint16 max;			///< Max level for this tree
+	uint16 need_level;	///< Homunculus level required
+	uint32 intimacy;	///< Intimacy required (n/100)
+	bool evolution;		///< Require evolution to show on skill tree
+	std::unordered_map<uint16, uint16> need; ///< Skills needed
+};
+
 struct s_homunculus_db {
 	int base_class, evo_class;
 	char name[NAME_LENGTH];
-	struct h_stats base, gmin, gmax, emin, emax;
+	struct s_hom_stats base, gmin, gmax, emin, emax;
 	int foodID;
-	int baseASPD;
-	long hungryDelay;
-	unsigned char element, race, base_size, evo_size;
+	uint16 baseASPD;
+	int hungryDelay;
+	e_element element;
+	e_race race;
+	e_size base_size, evo_size;
+	std::vector<s_homun_skill_tree_entry> skill_tree;
 };
-extern struct s_homunculus_db homunculus_db[MAX_HOMUNCULUS_CLASS];
-
-enum e_hom_search_type : uint8  { HOMUNCULUS_CLASS, HOMUNCULUS_FOOD };
 
 enum e_hom_mode : uint8  { MH_MD_FIGHTING = 1, MH_MD_GRAPPLING };
 
@@ -79,7 +91,7 @@ struct homun_data {
 	struct status_data base_status, battle_status;
 	status_change sc;
 	struct regen_data regen;
-	struct s_homunculus_db *homunculusDB;	//[orn]
+	std::shared_ptr<s_homunculus_db> homunculusDB;	//[orn]
 	struct s_homunculus homunculus;	//[orn]
 
 	int masterteleport_timer;
@@ -89,21 +101,6 @@ struct homun_data {
 	std::vector<uint16> blockskill;	// [orn]
 };
 
-#define MAX_HOM_SKILL_REQUIRE 5
-#define MAX_HOM_SKILL_TREE 10
-
-/// Homunculus skill entry [Celest]
-struct homun_skill_tree_entry {
-	uint16 id;			///< Skill ID
-	uint8 max;			///< Max level for this tree
-	uint8 need_level;	///< Homunculus level required
-	uint16 intimacy;	///< Intimacy required (n/100)
-	struct {
-		uint16 id;		///< Skill ID
-		uint8 lv;		///< Level of skill
-	} need[MAX_HOM_SKILL_REQUIRE]; ///< Skills needed
-};
-
 #define HOM_EVO 0x100 //256
 #define HOM_S 0x200 //512
 #define HOM_REG 0x1000 //4096
@@ -128,6 +125,33 @@ enum homun_mapid {
 	MAPID_ELANOR,
 };
 
+/// Homunculus class constants
+enum e_homun_classid : uint16 {
+	MER_LIF = 6001,
+	MER_AMISTR,
+	MER_FILIR,
+	MER_VANILMIRTH,
+	MER_LIF2,
+	MER_AMISTR2,
+	MER_FILIR2,
+	MER_VANILMIRTH2,
+	MER_LIF_H,
+	MER_AMISTR_H,
+	MER_FILIR_H,
+	MER_VANILMIRTH_H,
+	MER_LIF_H2,
+	MER_AMISTR_H2,
+	MER_FILIR_H2,
+	MER_VANILMIRTH_H2,
+
+	// Homunculus S
+	MER_EIRA = 6048,
+	MER_BAYERI,
+	MER_SERA,
+	MER_DIETER,
+	MER_ELEANOR,
+};
+
 /// Homunculus type
 enum homun_type : int8 {
 	HT_REG		= 0x1,
@@ -158,6 +182,24 @@ enum e_homun_grade : uint8 {
 	HOMGRADE_LOYAL,
 };
 
+class HomunculusDatabase : public TypesafeYamlDatabase<int32, s_homunculus_db> {
+private:
+	bool parseStatusNode(const std::string &nodeName, const std::string &subNodeName, const ryml::NodeRef &node, s_hom_stats &bonus);
+
+public:
+	HomunculusDatabase() : TypesafeYamlDatabase("HOMUNCULUS_DB", 1) {
+
+	}
+
+	const std::string getDefaultLocation();
+	uint64 parseBodyNode(const ryml::NodeRef& node);
+
+	// Additional
+	std::shared_ptr<s_homunculus_db> homun_search(int32 class_);
+};
+
+extern HomunculusDatabase homunculus_db;
+
 /// Check Homunculus Class ID
 #define homdb_checkid(id) ((id) >=  HM_CLASS_BASE && (id) <= HM_CLASS_MAX)
 
@@ -170,9 +212,9 @@ enum homun_type hom_class2type(int class_);
 void hom_damage(struct homun_data *hd);
 int hom_dead(struct homun_data *hd);
 void hom_skillup(struct homun_data *hd,uint16 skill_id);
-void hom_calc_skilltree(struct homun_data *hd, bool flag_evolve);
+void hom_calc_skilltree(homun_data *hd);
 short hom_checkskill(struct homun_data *hd,uint16 skill_id);
-uint8 hom_skill_get_min_level(int class_, uint16 skill_id);
+uint16 hom_skill_get_min_level(int class_, uint16 skill_id);
 void hom_gainexp(struct homun_data *hd,t_exp exp);
 int hom_levelup(struct homun_data *hd);
 int hom_evolution(struct homun_data *hd);
@@ -186,7 +228,6 @@ int hom_shuffle(struct homun_data *hd); // [Zephyrus]
 void hom_save(struct homun_data *hd);
 bool hom_call(map_session_data *sd);
 bool hom_create_request(map_session_data *sd, int class_);
-int hom_search(int key,int type);
 void hom_menu(map_session_data *sd,int type);
 int hom_food(map_session_data *sd, struct homun_data *hd);
 int hom_hungry_timer_delete(struct homun_data *hd);
@@ -198,7 +239,6 @@ int hom_increase_intimacy(struct homun_data * hd, unsigned int value);
 int hom_decrease_intimacy(struct homun_data * hd, unsigned int value);
 int hom_skill_tree_get_max(int skill_id, int b_class);
 void hom_init_timers(struct homun_data * hd);
-void hom_reload_skill(void);
 void hom_reload(void);
 
 void hom_addspiritball(TBL_HOM *hd, int max);

+ 1 - 0
src/map/itemdb.hpp

@@ -48,6 +48,7 @@ enum item_itemid : t_itemid
 	ITEMID_APPLE						= 512,
 	ITEMID_HOLY_WATER					= 523,
 	ITEMID_PUMPKIN						= 535,
+	ITEMID_PET_FOOD						= 537,
 	ITEMID_RED_SLIM_POTION				= 545,
 	ITEMID_YELLOW_SLIM_POTION			= 546,
 	ITEMID_WHITE_SLIM_POTION			= 547,

+ 1 - 2
src/map/map-server.vcxproj

@@ -334,8 +334,7 @@
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\exp_homun.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\exp_homun.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\exp_guild.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\exp_guild.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\guild_skill_tree.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\guild_skill_tree.yml')" />
-    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\homun_skill_tree.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homun_skill_tree.txt')" />
-    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\homunculus_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homunculus_db.txt')" />
+    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\homunculus_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\homunculus_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\instance_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\instance_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_cash.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_cash.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\item_combos.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\item_combos.yml')" />

+ 23 - 0
src/map/script_constants.hpp

@@ -10098,6 +10098,29 @@
 	export_constant(WOE_SECOND_EDITION);
 	export_constant(WOE_THIRD_EDITION);
 
+	/* homunculus view IDs */
+	export_constant(MER_LIF);
+	export_constant(MER_AMISTR);
+	export_constant(MER_FILIR);
+	export_constant(MER_VANILMIRTH);
+	export_constant(MER_LIF2);
+	export_constant(MER_AMISTR2);
+	export_constant(MER_FILIR2);
+	export_constant(MER_VANILMIRTH2);
+	export_constant(MER_LIF_H);
+	export_constant(MER_AMISTR_H);
+	export_constant(MER_FILIR_H);
+	export_constant(MER_VANILMIRTH_H);
+	export_constant(MER_LIF_H2);
+	export_constant(MER_AMISTR_H2);
+	export_constant(MER_FILIR_H2);
+	export_constant(MER_VANILMIRTH_H2);
+	export_constant(MER_EIRA);
+	export_constant(MER_BAYERI);
+	export_constant(MER_SERA);
+	export_constant(MER_DIETER);
+	export_constant(MER_ELEANOR);
+
 	#undef export_constant
 	#undef export_constant2
 	#undef export_parameter

+ 21 - 20
src/map/status.cpp

@@ -4898,26 +4898,27 @@ int status_calc_mercenary_(s_mercenary_data *md, uint8 opt)
 int status_calc_homunculus_(struct homun_data *hd, uint8 opt)
 {
 	struct status_data *status = &hd->base_status;
-	struct s_homunculus *hom = &hd->homunculus;
+	struct s_homunculus &hom = hd->homunculus;
 	int skill_lv;
 	int amotion;
 
-	status->str = hom->str / 10;
-	status->agi = hom->agi / 10;
-	status->vit = hom->vit / 10;
-	status->dex = hom->dex / 10;
-	status->int_ = hom->int_ / 10;
-	status->luk = hom->luk / 10;
+	status->str = hom.str / 10;
+	status->agi = hom.agi / 10;
+	status->vit = hom.vit / 10;
+	status->dex = hom.dex / 10;
+	status->int_ = hom.int_ / 10;
+	status->luk = hom.luk / 10;
 
 	APPLY_HOMUN_LEVEL_STATWEIGHT();
 
 	if (opt&SCO_FIRST) {
-		const struct s_homunculus_db *db = hd->homunculusDB;
+		const std::shared_ptr<s_homunculus_db> db = hd->homunculusDB;
+
 		status->def_ele = db->element;
 		status->ele_lv = 1;
 		status->race = db->race;
 		status->class_ = CLASS_NORMAL;
-		status->size = (hom->class_ == db->evo_class) ? db->evo_size : db->base_size;
+		status->size = (hom.class_ == db->evo_class) ? db->evo_size : db->base_size;
 		status->rhw.range = 1 + status->size;
 		status->mode = static_cast<e_mode>(MD_CANMOVE|MD_CANATTACK);
 		status->speed = DEFAULT_WALK_SPEED;
@@ -4932,13 +4933,13 @@ int status_calc_homunculus_(struct homun_data *hd, uint8 opt)
 
 #ifdef RENEWAL
 	amotion = hd->homunculusDB->baseASPD;
-	amotion = amotion - amotion * (status->dex + hom->dex_value) / 1000 - (status->agi + hom->agi_value) * amotion / 250;
+	amotion = amotion - amotion * (status->dex + hom.dex_value) / 1000 - (status->agi + hom.agi_value) * amotion / 250;
 	status->def = status->mdef = 0;
 #else
-	skill_lv = hom->level / 10 + status->vit / 5;
+	skill_lv = hom.level / 10 + status->vit / 5;
 	status->def = cap_value(skill_lv, 0, 99);
 
-	skill_lv = hom->level / 10 + status->int_ / 5;
+	skill_lv = hom.level / 10 + status->int_ / 5;
 	status->mdef = cap_value(skill_lv, 0, 99);
 
 	amotion = (1000 - 4 * status->agi - status->dex) * hd->homunculusDB->baseASPD / 1000;
@@ -4947,10 +4948,10 @@ int status_calc_homunculus_(struct homun_data *hd, uint8 opt)
 	status->amotion = cap_value(amotion, battle_config.max_aspd, 2000);
 	status->adelay = status->amotion; //It seems adelay = amotion for Homunculus.
 
-	status->max_hp = hom->max_hp;
-	status->max_sp = hom->max_sp;
+	status->max_hp = hom.max_hp;
+	status->max_sp = hom.max_sp;
 
-	hom_calc_skilltree(hd, 0);
+	hom_calc_skilltree(hd);
 
 	if((skill_lv = hom_checkskill(hd, HAMI_SKIN)) > 0)
 		status->def += skill_lv * 4;
@@ -4992,18 +4993,18 @@ int status_calc_homunculus_(struct homun_data *hd, uint8 opt)
 	}
 
 	if (opt&SCO_FIRST) {
-		hd->battle_status.hp = hom->hp;
-		hd->battle_status.sp = hom->sp;
-		if(hom->class_ == 6052) // Eleanor
+		hd->battle_status.hp = hom.hp;
+		hd->battle_status.sp = hom.sp;
+		if(hom.class_ == 6052) // Eleanor
 			sc_start(&hd->bl,&hd->bl, SC_STYLE_CHANGE, 100, MH_MD_FIGHTING, INFINITE_TICK);
 	}
 
 #ifndef RENEWAL
 	status->rhw.atk = status->dex;
-	status->rhw.atk2 = status->str + hom->level;
+	status->rhw.atk2 = status->str + hom.level;
 #endif
 
-	status_calc_misc(&hd->bl, status, hom->level);
+	status_calc_misc(&hd->bl, status, hom.level);
 
 	status_cpy(&hd->battle_status, status);
 	return 1;

+ 308 - 0
src/tool/csv2yaml.cpp

@@ -56,6 +56,14 @@ static void item_txt_data(const std::string& modePath, const std::string& fixedP
 		sv_readdb(modePath.c_str(), "item_trade.txt", ',', 3, 3, -1, &itemdb_read_itemtrade, false);
 }
 
+// Homunculus database data to memory
+static void homunculus_txt_data(const std::string& modePath, const std::string& fixedPath) {
+	hom_skill_tree.clear();
+
+	if (fileExists(modePath + "/homun_skill_tree.txt"))
+		sv_readdb(modePath.c_str(), "homun_skill_tree.txt", ',', 15, 15, -1, read_homunculus_skilldb, false);
+}
+
 // Mob database data to memory
 static void mob_txt_data(const std::string &modePath, const std::string &fixedPath) {
 	mob_race2.clear();
@@ -546,6 +554,20 @@ bool Csv2YamlTool::initialize( int argc, char* argv[] ){
 		return 0;
 	}
 
+	homunculus_txt_data(path_db, path_db);
+	if (!process("HOMUNCULUS_DB", 1, { path_db_mode }, "homunculus_db", [](const std::string& path, const std::string& name_ext) -> bool {
+		return sv_readdb(path.c_str(), name_ext.c_str(), ',', 50, 50, MAX_HOMUNCULUS_CLASS, read_homunculusdb, false);
+	})) {
+		return 0;
+	}
+	
+	homunculus_txt_data(path_db_import, path_db_import);
+	if (!process("HOMUNCULUS_DB", 1, { path_db_import }, "homunculus_db", [](const std::string& path, const std::string& name_ext) -> bool {
+		return sv_readdb(path.c_str(), name_ext.c_str(), ',', 50, 50, MAX_HOMUNCULUS_CLASS, read_homunculusdb, false);
+	})) {
+		return 0;
+	}
+
 	// TODO: add implementations ;-)
 
 	return true;
@@ -4970,6 +4992,292 @@ static bool cashshop_parse_dbrow( char* fields[], int columns, int current ){
 	return true;
 }
 
+// homunculus_db.yml function
+//---------------------------
+static bool read_homunculus_skilldb(char* split[], int columns, int current) {
+	s_homun_skill_tree_entry entry = {};
+
+	entry.id = atoi(split[1]);
+	entry.max = atoi(split[2]);
+	entry.need_level = atoi(split[3]);
+	entry.intimacy = cap_value(atoi(split[14]), 0, 1000);
+
+	for (int i = 0; i < MAX_HOM_SKILL_REQUIRE; i++) {
+		if (atoi(split[4 + i * 2]) > 0)
+			entry.need.emplace(atoi(split[4 + i * 2]), atoi(split[4 + i * 2 + 1]));
+	}
+
+	if (util::umap_find(hom_skill_tree, atoi(split[0])))
+		hom_skill_tree[(uint16)atoi(split[0])].push_back(entry);
+	else {
+		hom_skill_tree[(uint16)atoi(split[0])] = std::vector<s_homun_skill_tree_entry>();
+		hom_skill_tree[(uint16)atoi(split[0])].push_back(entry);
+	}
+
+	return true;
+}
+
+static bool compareHomSkillId(const s_homun_skill_tree_entry &a, const s_homun_skill_tree_entry &b) {
+	return a.id < b.id;
+}
+
+// Copied and adjusted from homunculus.cpp
+static bool read_homunculusdb(char* str[], int columns, int current) {
+	bool has_evo = false;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Class" << YAML::Value << name2Upper(constant_lookup(atoi(str[0]), "MER_") + 4);
+	body << YAML::Key << "Name" << YAML::Value << str[2];
+	if (atoi(str[0]) != atoi(str[1])) {
+		body << YAML::Key << "EvolutionClass" << YAML::Value << name2Upper(constant_lookup(atoi(str[1]), "MER_") + 4);
+		has_evo = true;
+	}
+	if (atoi(str[3]) != ITEMID_PET_FOOD)
+		body << YAML::Key << "Food" << YAML::Value << name2Upper(*util::umap_find(aegis_itemnames, (t_itemid)atoi(str[3])));
+	if (atoi(str[4]) != 60000)
+		body << YAML::Key << "HungryDelay" << YAML::Value << atoi(str[4]);
+
+	if (atoi(str[7]) != RC_DEMIHUMAN)
+		body << YAML::Key << "Race" << YAML::Value << name2Upper(constant_lookup(atoi(str[7]), "RC_") + 3);
+	if (atoi(str[8]) != ELE_NEUTRAL)
+	body << YAML::Key << "Element" << YAML::Value << name2Upper(constant_lookup(atoi(str[8]), "ELE_") + 4);
+	if (atoi(str[5]) != SZ_SMALL)
+		body << YAML::Key << "Size" << YAML::Value << name2Upper(constant_lookup(atoi(str[5]), "Size_") + 5);
+	if (atoi(str[6]) != SZ_MEDIUM)
+		body << YAML::Key << "EvolutionSize" << YAML::Value << name2Upper(constant_lookup(atoi(str[6]), "Size_") + 5);
+	if (atoi(str[9]) != 700)
+		body << YAML::Key << "AttackDelay" << YAML::Value << atoi(str[9]);
+
+	body << YAML::Key << "Status";
+	body << YAML::BeginSeq;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Hp";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[10]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[18]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[19]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[34]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[35]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Sp";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[11]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[20]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[21]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[36]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[37]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Str";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[12]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[22]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[23]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[38]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[39]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Agi";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[13]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[24]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[25]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[40]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[41]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Vit";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[14]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[26]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[27]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[42]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[43]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Int";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[15]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[28]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[29]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[44]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[45]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Dex";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[16]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[30]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[31]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[46]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[47]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::BeginMap;
+	body << YAML::Key << "Type" << YAML::Value << "Luk";
+	body << YAML::Key << "Base" << YAML::Value << atoi(str[17]);
+	body << YAML::Key << "GrowthMinimum" << YAML::Value << atoi(str[32]);
+	body << YAML::Key << "GrowthMaximum" << YAML::Value << atoi(str[33]);
+	if (has_evo) {
+		body << YAML::Key << "EvolutionMinimum" << YAML::Value << atoi(str[48]);
+		body << YAML::Key << "EvolutionMaximum" << YAML::Value << atoi(str[49]);
+	}
+	body << YAML::EndMap;
+
+	body << YAML::EndSeq;
+
+	// Gather and sort skill tree data
+	std::vector<s_homun_skill_tree_entry> *skill_tree_base = nullptr, *skill_tree_evo = nullptr;
+
+	skill_tree_base = util::umap_find(hom_skill_tree, atoi(str[0]));
+	std::sort(skill_tree_base->begin(), skill_tree_base->end(), compareHomSkillId);
+
+	if (has_evo) {
+		skill_tree_evo = util::umap_find(hom_skill_tree, atoi(str[1]));
+		std::sort(skill_tree_evo->begin(), skill_tree_evo->end(), compareHomSkillId);
+	}
+
+	// Get a difference between the base class and evo class skill tree to find skills granted through evolution
+	std::vector<s_homun_skill_tree_entry> tree_diff;
+
+	if (skill_tree_evo != nullptr) {
+		for (const auto &evoit : *skill_tree_evo) {
+			bool contains = false;
+
+			for (const auto &baseit : *skill_tree_base) {
+				if (baseit.id == evoit.id) {
+					contains = true;
+					break;
+				}
+			}
+
+			if (!contains) {
+				// Skill is not part of the base tree, we can only assume it's an evolution granted skill thanks to brilliant formatting of our TXT homun_db!
+				tree_diff.push_back(evoit);
+			}
+		}
+	}
+
+	if (skill_tree_base != nullptr) {
+		body << YAML::Key << "SkillTree";
+		body << YAML::BeginSeq;
+
+		for (const auto &skillit : *skill_tree_base) {
+			std::string *skill_name = util::umap_find(aegis_skillnames, skillit.id);
+
+			if (skill_name == nullptr) {
+				ShowError("Skill name for homunculus skill ID %hu is not known.\n", skillit.id);
+				return false;
+			}
+
+			body << YAML::BeginMap;
+			body << YAML::Key << "Skill" << YAML::Value << *skill_name;
+			body << YAML::Key << "MaxLevel" << YAML::Value << (int)skillit.max;
+			if (skillit.need_level > 0)
+				body << YAML::Key << "RequiredLevel" << YAML::Value << (int)skillit.need_level;
+			if (skillit.intimacy > 0)
+				body << YAML::Key << "RequiredIntimacy" << YAML::Value << skillit.intimacy;
+
+			if (!skillit.need.empty()) {
+				body << YAML::Key << "Required";
+				body << YAML::BeginSeq;
+
+				for (const auto &it : skillit.need) {
+					uint16 required_skill_id = it.first;
+					uint16 required_skill_level = it.second;
+
+					if (required_skill_id == 0 || required_skill_level == 0)
+						continue;
+
+					std::string *required_name = util::umap_find(aegis_skillnames, required_skill_id);
+
+					if (required_name == nullptr) {
+						ShowError("Skill name for required skill id %hu is not known.\n", required_skill_id);
+						return false;
+					}
+
+					body << YAML::BeginMap;
+					body << YAML::Key << "Skill" << YAML::Value << *required_name;
+					body << YAML::Key << "Level" << YAML::Value << required_skill_level;
+					body << YAML::EndMap;
+				}
+
+				body << YAML::EndSeq;
+			}
+
+			body << YAML::EndMap;
+		}
+
+		for (const auto &skillit : tree_diff) {
+			std::string *skill_name = util::umap_find(aegis_skillnames, skillit.id);
+
+			if (skill_name == nullptr) {
+				ShowError("Skill name for homunculus skill ID %hu is not known.\n", skillit.id);
+				return false;
+			}
+
+			body << YAML::BeginMap;
+			body << YAML::Key << "Skill" << YAML::Value << *skill_name;
+			body << YAML::Key << "MaxLevel" << YAML::Value << (int)skillit.max;
+			if (skillit.need_level > 0)
+				body << YAML::Key << "RequiredLevel" << YAML::Value << (int)skillit.need_level;
+			if (skillit.intimacy > 0)
+				body << YAML::Key << "RequiredIntimacy" << YAML::Value << skillit.intimacy;
+			body << YAML::Key << "RequireEvolution" << YAML::Value << "true";
+
+			if (!skillit.need.empty()) {
+				body << YAML::Key << "Required";
+				body << YAML::BeginSeq;
+
+				for (const auto &it : skillit.need) {
+					uint16 required_skill_id = it.first;
+					uint16 required_skill_level = it.second;
+
+					if (required_skill_id == 0 || required_skill_level == 0)
+						continue;
+
+					std::string *required_name = util::umap_find(aegis_skillnames, required_skill_id);
+
+					if (required_name == nullptr) {
+						ShowError("Skill name for required skill id %hu is not known.\n", required_skill_id);
+						return false;
+					}
+
+					body << YAML::BeginMap;
+					body << YAML::Key << "Skill" << YAML::Value << *required_name;
+					body << YAML::Key << "Level" << YAML::Value << required_skill_level;
+					body << YAML::EndMap;
+				}
+
+				body << YAML::EndSeq;
+			}
+
+			body << YAML::EndMap;
+		}
+
+		body << YAML::EndSeq;
+	}
+
+	body << YAML::EndMap;
+
+	return true;
+}
+
 int main( int argc, char *argv[] ){
 	return main_core<Csv2YamlTool>( argc, argv );
 }

+ 5 - 0
src/tool/csv2yaml.hpp

@@ -41,6 +41,7 @@ namespace rathena{
 #define MAX_PC_SKILL_REQUIRE 5 /// Max skill tree requirement
 ///Maximum amount of items a combo may require
 #define MAX_ITEMS_PER_COMBO 6
+#define MAX_HOM_SKILL_REQUIRE 5
 
 struct s_skill_tree_entry_csv {
 	std::string skill_name;
@@ -62,6 +63,8 @@ std::unordered_map<uint16, s_skill_unit_csv> skill_unit;
 std::unordered_map<uint16, s_skill_copyable> skill_copyable;
 std::unordered_map<uint16, s_skill_db> skill_nearnpc;
 
+std::unordered_map<int32, std::vector<s_homun_skill_tree_entry>> hom_skill_tree;
+
 static unsigned int level_penalty[3][CLASS_MAX][MAX_LEVEL * 2 + 1];
 
 struct s_item_flag_csv2yaml {
@@ -531,5 +534,7 @@ static bool pc_readdb_skilltree(char* str[], int columns, int current);
 static bool pc_readdb_skilltree_yaml(void);
 static bool itemdb_read_combos(const char* file);
 static bool cashshop_parse_dbrow( char* fields[], int columns, int current );
+static bool read_homunculus_skilldb(char* split[], int columns, int current);
+static bool read_homunculusdb(char* str[], int columns, int current);
 
 #endif /* CSV2YAML_HPP */

+ 2 - 1
src/tool/yaml.hpp

@@ -45,8 +45,9 @@
 #include "../map/channel.hpp"
 #include "../map/chat.hpp"
 #include "../map/date.hpp"
-#include "../map/instance.hpp"
 #include "../map/elemental.hpp"
+#include "../map/homunculus.hpp"
+#include "../map/instance.hpp"
 #include "../map/mercenary.hpp"
 #include "../map/mob.hpp"
 #include "../map/npc.hpp"

+ 1 - 0
src/tool/yaml2sql.cpp

@@ -43,6 +43,7 @@
 #include "../map/chat.hpp"
 #include "../map/date.hpp"
 #include "../map/elemental.hpp"
+#include "../map/homunculus.hpp"
 #include "../map/instance.hpp"
 #include "../map/mercenary.hpp"
 #include "../map/mob.hpp"