浏览代码

Converted mercenary_db to YAML format (#6101)

* Converts the Mercenary Tables file into YAML.
* Includes CSV2YAML converter.
* Fixed mercenary name not properly displayed after moving

Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Co-authored-by: Lemongrass3110 <lemongrass@kstp.at>
Atemo 3 年之前
父节点
当前提交
4a778815a3

+ 0 - 5
db/import-tmpl/mercenary_db.txt

@@ -1,5 +0,0 @@
-// Mercenary Database
-//
-// Structure of Database:
-// ID,Sprite_Name,Name,LV,HP,SP,Range1,ATK1,ATK2,DEF,MDEF,STR,AGI,VIT,INT,DEX,LUK,Range2,Range3,Scale,Race,Element,Speed,aDelay,aMotion,dMotion
-

+ 59 - 0
db/import-tmpl/mercenary_db.yml

@@ -0,0 +1,59 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2021 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/>.
+#
+###########################################################################
+# Mercenary Database
+###########################################################################
+#
+# Mercenary Settings
+#
+###########################################################################
+# - Id                      Mercenary ID.
+#   AegisName               Server name to reference the mercenary in scripts and lookups, should use no spaces.
+#   Name                    Name in English.
+#   Level                   Level. (Default: 1)
+#   Hp                      Total HP. (Default: 1)
+#   Sp                      Total SP. (Default: 1)
+#   Attack                  Minimum attack. (Default: 0)
+#   Attack2                 Maximum attack. (Default: 0)
+#   Defense                 Physical defense of the mercenary, reduces melee and ranged physical attack/skill damage. (Default: 0)
+#   MagicDefense            Magic defense of the mercenary, reduces magical skill damage. (Default: 0)
+#   Str                     Strength which affects attack. (Default: 1)
+#   Agi                     Agility which affects flee. (Default: 1)
+#   Vit                     Vitality which affects defense. (Default: 1)
+#   Int                     Intelligence which affects magic attack. (Default: 1)
+#   Dex                     Dexterity which affects hit rate. (Default: 1)
+#   Luk                     Luck which affects perfect dodge/lucky flee/perfect flee/lucky dodge rate. (Default: 1)
+#   AttackRange             Attack range. (Default: 0)
+#   SkillRange              Skill cast range. (Default: 0)
+#   ChaseRange              Chase range. (Default: 0)
+#   Size                    Size. (Default: Small)
+#   Race                    Race. (Default: Formless)
+#   Element                 Element. (Default: Neutral)
+#   ElementLevel            Level of element. (Default: 1)
+#   WalkSpeed               Walk speed. (Default: DEFAULT_WALK_SPEED)
+#   AttackDelay             Attack speed. (Default: 4000)
+#   AttackMotion            Attack animation speed. (Default: 2000)
+#   DamageMotion            Damage animation speed. (Default: 0)
+#   Skills:                 List of mercenary skills. (Optional)
+#     - Name                Skill name.
+#       MaxLevel            Max skill level.
+###########################################################################
+
+Header:
+  Type: MERCENARY_DB
+  Version: 1

+ 0 - 5
db/import-tmpl/mercenary_skill_db.txt

@@ -1,5 +0,0 @@
-// Mercenary Skill Database
-//
-// Structure of Database:
-// MercenryID,SkillID,SkillLevel
-

+ 0 - 71
db/mercenary_db.txt

@@ -1,71 +0,0 @@
-// Mercenary Database
-//
-// Structure of Database:
-// ID,Sprite_Name,Name,LV,HP,SP,Range1,ATK1,ATK2,DEF,MDEF,STR,AGI,VIT,INT,DEX,LUK,Range2,Range3,Scale,Race,Element,Speed,aDelay,aMotion,dMotion
-
-// Monster Mercenaries
-1191,MIMIC,Mimic,51,6120,187,2,150,900,10,40,44,121,1,60,75,110,10,12,1,0,60,100,972,500,288
-1506,DISGUISE,Disguise,55,7543,180,2,279,546,18,29,0,72,45,35,48,65,10,12,1,6,82,147,516,768,384
-1275,ALICE,Alice,62,10000,221,2,550,700,5,5,64,64,42,85,100,130,10,12,1,7,60,200,502,2304,480
-1965,M_WILD_ROSE,Wild Rose,38,2980,130,2,315,360,0,15,65,85,15,35,65,80,10,12,0,2,24,100,964,864,288
-1966,M_DOPPELGANGER,Doppelganger,72,7800,200,2,1340,1590,60,35,88,90,30,35,125,65,10,12,1,6,67,100,480,480,288
-1967,M_YGNIZEM,Egnigem Cenia,58,11200,320,2,823,1212,35,8,60,35,52,18,79,20,10,12,1,7,43,145,576,432,288
-2000,M_GAMEMASTER,Male Game Master,50,7000,250,2,100,50,6,17,1,109,1,60,215,111,10,0,0,7,20,150,450,432,300
-2001,F_GAMEMASTER,Female Game Master,50,7000,250,2,100,50,6,17,1,109,1,60,215,111,10,0,0,7,20,150,450,432,300
-2034,M_DESERT_WOLF_B,Baby Desert Wolf,9,164,15,1,500,600,0,0,1,9,9,5,40,40,10,12,0,2,23,100,1600,900,240
-2037,VALKYRIE_A,Valkyrie Randgris,90,5000,15,1,10,160,10,20,1,20,40,0,20,20,10,12,1,8,66,100,576,576,480
-2038,VALKYRIE_B,Valkyrie Randgris,90,10000,15,1,300,450,10,40,1,20,80,0,80,20,10,12,1,8,66,100,576,576,480
-2058,M_MIMIC,Mimic,51,6120,182,1,800,950,10,40,44,121,1,60,75,110,10,12,1,0,60,100,972,500,288
-2059,M_DISGUISE,Disguise,55,7543,180,2,526,693,18,29,0,72,45,35,48,65,10,12,1,6,82,147,516,768,384
-2060,M_ALICE,Alice,62,10000,221,1,700,850,5,5,64,64,42,85,100,130,10,12,1,7,60,200,502,1999,480
-2213,M_WANDER_MAN,Wander Man,81,8614,220,2,1100,1300,60,20,80,110,63,51,85,90,10,12,1,6,24,100,672,500,192
-2214,M_WICKED_NYMPH,Wicked Nymph,85,6157,256,2,420,620,30,45,40,50,40,92,60,110,10,12,1,6,67,200,637,1008,360
-2215,M_KASA,Kasa,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,2,0,63,150,800,600,288
-2216,M_SALAMANDER,Salamander,87,9517,260,2,900,1100,60,68,90,80,65,45,87,95,10,12,2,0,63,160,140,384,288
-2217,M_TEDDY_BEAR,Teddy Bear,85,14493,243,1,600,800,100,70,60,20,85,50,75,130,10,12,0,0,60,200,512,780,504
-2325,M_BAPHOMET_,Baphomet,57,7510,204,1,810,955,70,40,52,60,36,17,57,25,10,12,0,6,27,100,868,480,120
-2326,M_GALAPAGO,Galapago,45,7513,201,1,760,915,70,40,30,28,29,18,30,16,10,12,0,2,22,165,1430,1080,1080
-2342,MER_DIABOLIC,Diabolic,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2344,MER_WISH_MAIDEN,Wish Maiden,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2345,MER_ZHERLTHSH,Zherlthsh,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2346,MER_KTULLANUX,Ktullanux,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2347,MER_EDDGA,Eddga,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2348,MER_CIVIL_SERVANT,Civil Servant,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2349,MER_LOLI_RURI,Loli Ruri,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2350,MER_SEDORA,Sedora,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2351,MER_CHEPET,Chepet,83,9815,234,2,1100,1300,60,60,85,90,71,43,85,105,10,12,0,6,47,150,1080,780,180
-2378,MER_ANTLER_SCARABA,Antler Scaraba,136,30000,1,1,1418,1828,155,102,23,99,59,129,137,45,10,12,1,4,42,200,504,624,360
-2937,M_LOKI,Loki's Shadow,145,1215600,1,2,1835,2279,15,89,76,66,90,55,189,22,10,12,1,7,20,175,800,750,300
-3087,M_NYDHOG,Guardian's Alter Ego,160,215000,1,2,1835,444,15,89,76,66,90,55,189,22,10,12,1,7,20,175,800,750,300
-
-// Normal Mercenaries
-6017,MER_ARCHER01,Mina,20,256,200,10,170,85,7,5,1,16,5,1,28,8,10,0,0,7,20,150,700,432,300
-6018,MER_ARCHER02,Dororu,30,457,70,10,228,114,11,7,1,18,8,1,40,11,10,0,0,7,20,150,700,432,300
-6019,MER_ARCHER03,Nami,40,732,93,10,260,130,15,9,3,21,12,4,52,17,10,0,0,7,20,150,700,432,300
-6020,MER_ARCHER04,Elfin,50,1092,116,10,310,155,18,11,5,33,17,6,60,23,10,0,0,7,20,150,575,432,300
-6021,MER_ARCHER05,Clara,60,2212,280,10,360,180,20,13,5,41,17,12,75,30,10,0,0,7,20,150,575,432,300
-6022,MER_ARCHER06,Dali,70,3098,353,10,424,212,21,15,11,46,24,22,83,37,10,0,0,7,20,150,575,432,300
-6023,MER_ARCHER07,Karaya,80,4051,415,10,468,234,22,16,14,55,27,26,91,44,10,0,0,7,20,150,450,432,300
-6024,MER_ARCHER08,Hiyori,90,5039,469,10,482,241,24,18,19,65,27,27,103,49,10,0,0,7,20,150,450,432,300
-6025,MER_ARCHER09,Kero,95,5572,499,10,500,250,25,20,20,71,27,28,110,51,10,0,0,7,20,150,450,432,300
-6026,MER_ARCHER10,Sukye,99,7381,642,10,816,308,49,49,21,83,27,28,123,52,10,0,0,7,20,150,450,432,300
-6027,MER_LANCER01,Rodin,22,2071,100,2,168,84,30,1,27,1,4,1,30,2,10,0,0,7,20,150,700,432,300
-6028,MER_LANCER02,Lancer,34,2523,131,2,208,104,33,1,37,1,4,1,37,4,10,0,0,7,20,150,700,432,300
-6029,MER_LANCER03,Nathan,41,3397,161,2,248,124,36,5,45,1,22,1,40,6,10,0,0,7,20,150,700,432,300
-6030,MER_LANCER04,Roan,55,4580,191,2,300,150,39,5,55,2,35,1,50,8,10,0,0,7,20,150,575,432,300
-6031,MER_LANCER05,Orizaro,60,5899,221,2,350,160,42,10,65,2,39,1,60,10,10,0,0,7,20,150,575,432,300
-6032,MER_LANCER06,Thyla,72,7874,252,2,370,185,46,10,75,3,15,77,61,12,10,0,0,7,20,150,575,432,300
-6033,MER_LANCER07,Ben,81,10260,330,2,380,190,50,15,85,3,63,20,61,14,10,0,0,7,20,150,450,432,300
-6034,MER_LANCER08,Pinaka,90,13167,366,2,400,200,55,20,95,20,74,20,63,16,10,0,0,7,20,150,450,432,300
-6035,MER_LANCER09,Kuhlmann,95,14648,398,2,440,220,60,25,100,25,77,25,63,18,10,0,0,7,20,150,450,432,300
-6036,MER_LANCER10,Roux,99,18000,413,2,700,250,70,30,120,30,90,30,70,30,10,0,0,7,20,150,450,432,300
-6037,MER_SWORDMAN01,David,20,502,70,2,174,87,26,0,21,27,5,1,30,3,10,0,0,7,20,150,700,432,300
-6038,MER_SWORDMAN02,Ellen,30,979,99,2,258,129,31,0,26,38,6,1,35,3,10,0,0,7,20,150,700,432,300
-6039,MER_SWORDMAN03,Luise,40,1555,134,2,326,163,36,3,31,45,7,6,40,6,10,0,0,7,20,150,700,432,300
-6040,MER_SWORDMAN04,Frank,50,2397,162,2,382,191,39,5,37,59,8,6,40,6,10,0,0,7,20,150,575,432,300
-6041,MER_SWORDMAN05,Ryan,60,3387,195,2,406,203,42,7,48,68,9,6,45,9,10,0,0,7,20,150,575,432,300
-6042,MER_SWORDMAN06,Paolo,70,4495,241,2,436,218,45,12,53,79,10,12,50,9,10,0,0,7,20,150,575,432,300
-6043,MER_SWORDMAN07,Jens,80,5889,279,2,468,234,47,15,59,88,11,12,55,12,10,0,0,7,20,150,450,432,300
-6044,MER_SWORDMAN08,Thierry,90,7520,325,2,500,250,49,18,70,95,12,18,60,15,10,0,0,7,20,150,450,432,300
-6045,MER_SWORDMAN09,Steven,95,9052,348,2,524,262,51,22,80,95,18,18,60,15,10,0,0,7,20,150,450,432,300
-6046,MER_SWORDMAN10,Wayne,99,12355,451,2,760,280,64,30,99,105,30,30,60,30,10,0,0,7,20,150,450,432,300

+ 1438 - 0
db/mercenary_db.yml

@@ -0,0 +1,1438 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2021 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/>.
+#
+###########################################################################
+# Mercenary Database
+###########################################################################
+#
+# Mercenary Settings
+#
+###########################################################################
+# - Id                      Mercenary ID.
+#   AegisName               Server name to reference the mercenary in scripts and lookups, should use no spaces.
+#   Name                    Name in English.
+#   Level                   Level. (Default: 1)
+#   Hp                      Total HP. (Default: 1)
+#   Sp                      Total SP. (Default: 1)
+#   Attack                  Minimum attack. (Default: 0)
+#   Attack2                 Maximum attack. (Default: 0)
+#   Defense                 Physical defense of the mercenary, reduces melee and ranged physical attack/skill damage. (Default: 0)
+#   MagicDefense            Magic defense of the mercenary, reduces magical skill damage. (Default: 0)
+#   Str                     Strength which affects attack. (Default: 1)
+#   Agi                     Agility which affects flee. (Default: 1)
+#   Vit                     Vitality which affects defense. (Default: 1)
+#   Int                     Intelligence which affects magic attack. (Default: 1)
+#   Dex                     Dexterity which affects hit rate. (Default: 1)
+#   Luk                     Luck which affects perfect dodge/lucky flee/perfect flee/lucky dodge rate. (Default: 1)
+#   AttackRange             Attack range. (Default: 0)
+#   SkillRange              Skill cast range. (Default: 0)
+#   ChaseRange              Chase range. (Default: 0)
+#   Size                    Size. (Default: Small)
+#   Race                    Race. (Default: Formless)
+#   Element                 Element. (Default: Neutral)
+#   ElementLevel            Level of element. (Default: 1)
+#   WalkSpeed               Walk speed. (Default: DEFAULT_WALK_SPEED)
+#   AttackDelay             Attack speed. (Default: 4000)
+#   AttackMotion            Attack animation speed. (Default: 2000)
+#   DamageMotion            Damage animation speed. (Default: 0)
+#   Skills:                 List of mercenary skills. (Optional)
+#     - Name                Skill name.
+#       MaxLevel            Max skill level.
+###########################################################################
+
+Header:
+  Type: MERCENARY_DB
+  Version: 1
+
+Body:
+  - Id: 1191
+    AegisName: MIMIC
+    Name: Mimic
+    Level: 51
+    Hp: 6120
+    Sp: 187
+    Attack: 150
+    Attack2: 900
+    Defense: 10
+    MagicDefense: 40
+    Str: 44
+    Agi: 121
+    Int: 60
+    Dex: 75
+    Luk: 110
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 972
+    AttackMotion: 500
+    DamageMotion: 288
+    Skills:
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+      - Name: ML_AUTOGUARD
+        MaxLevel: 5
+      - Name: MS_BASH
+        MaxLevel: 5
+  - Id: 1506
+    AegisName: DISGUISE
+    Name: Disguise
+    Level: 55
+    Hp: 7543
+    Sp: 180
+    Attack: 279
+    Attack2: 546
+    Defense: 18
+    MagicDefense: 29
+    Str: 0
+    Agi: 72
+    Vit: 45
+    Int: 35
+    Dex: 48
+    Luk: 65
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Earth
+    ElementLevel: 4
+    WalkSpeed: 147
+    AttackDelay: 516
+    AttackMotion: 768
+    DamageMotion: 384
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 2
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MER_LEXDIVINA
+        MaxLevel: 3
+  - Id: 1275
+    AegisName: ALICE
+    Name: Alice
+    Level: 62
+    Hp: 10000
+    Sp: 221
+    Attack: 550
+    Attack2: 700
+    Defense: 5
+    MagicDefense: 5
+    Str: 64
+    Agi: 64
+    Vit: 42
+    Int: 85
+    Dex: 100
+    Luk: 130
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 200
+    AttackDelay: 502
+    AttackMotion: 2304
+    DamageMotion: 480
+    Skills:
+      - Name: MER_PROVOKE
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: ML_BRANDISH
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+  - Id: 1965
+    AegisName: M_WILD_ROSE
+    Name: Wild Rose
+    Level: 38
+    Hp: 2980
+    Sp: 130
+    Attack: 315
+    Attack2: 360
+    MagicDefense: 15
+    Str: 65
+    Agi: 85
+    Vit: 15
+    Int: 35
+    Dex: 65
+    Luk: 80
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Brute
+    Element: Wind
+    ElementLevel: 1
+    WalkSpeed: 100
+    AttackDelay: 964
+    AttackMotion: 864
+    DamageMotion: 288
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 5
+  - Id: 1966
+    AegisName: M_DOPPELGANGER
+    Name: Doppelganger
+    Level: 72
+    Hp: 7800
+    Sp: 200
+    Attack: 1340
+    Attack2: 1590
+    Defense: 60
+    MagicDefense: 35
+    Str: 88
+    Agi: 90
+    Vit: 30
+    Int: 35
+    Dex: 125
+    Luk: 65
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Dark
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 480
+    AttackMotion: 480
+    DamageMotion: 288
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 5
+      - Name: MS_BASH
+        MaxLevel: 5
+      - Name: ML_DEVOTION
+        MaxLevel: 3
+  - Id: 1967
+    AegisName: M_YGNIZEM
+    Name: Egnigem Cenia
+    Level: 58
+    Hp: 11200
+    Sp: 320
+    Attack: 823
+    Attack2: 1212
+    Defense: 35
+    MagicDefense: 8
+    Str: 60
+    Agi: 35
+    Vit: 52
+    Int: 18
+    Dex: 79
+    Luk: 20
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Fire
+    ElementLevel: 2
+    WalkSpeed: 145
+    AttackDelay: 576
+    AttackMotion: 432
+    DamageMotion: 288
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 5
+      - Name: MS_BASH
+        MaxLevel: 5
+      - Name: ML_DEVOTION
+        MaxLevel: 3
+  - Id: 2000
+    AegisName: M_GAMEMASTER
+    Name: Male Game Master
+    Level: 50
+    Hp: 7000
+    Sp: 250
+    Attack: 100
+    Attack2: 50
+    Defense: 6
+    MagicDefense: 17
+    Agi: 109
+    Int: 60
+    Dex: 215
+    Luk: 111
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_TENDER
+        MaxLevel: 1
+      - Name: MER_KYRIE
+        MaxLevel: 5
+      - Name: MER_BLESSING
+        MaxLevel: 5
+      - Name: MER_INCAGI
+        MaxLevel: 5
+  - Id: 2001
+    AegisName: F_GAMEMASTER
+    Name: Female Game Master
+    Level: 50
+    Hp: 7000
+    Sp: 250
+    Attack: 100
+    Attack2: 50
+    Defense: 6
+    MagicDefense: 17
+    Agi: 109
+    Int: 60
+    Dex: 215
+    Luk: 111
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_TENDER
+        MaxLevel: 1
+      - Name: MER_KYRIE
+        MaxLevel: 5
+      - Name: MER_BLESSING
+        MaxLevel: 5
+      - Name: MER_INCAGI
+        MaxLevel: 5
+  - Id: 2034
+    AegisName: M_DESERT_WOLF_B
+    Name: Baby Desert Wolf
+    Level: 9
+    Hp: 164
+    Sp: 15
+    Attack: 500
+    Attack2: 600
+    Agi: 9
+    Vit: 9
+    Int: 5
+    Dex: 40
+    Luk: 40
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Brute
+    Element: Fire
+    ElementLevel: 1
+    WalkSpeed: 100
+    AttackDelay: 1600
+    AttackMotion: 900
+    DamageMotion: 240
+  - Id: 2037
+    AegisName: VALKYRIE_A
+    Name: Valkyrie Randgris
+    Level: 90
+    Hp: 5000
+    Sp: 15
+    Attack: 10
+    Attack2: 160
+    Defense: 10
+    MagicDefense: 20
+    Agi: 20
+    Vit: 40
+    Int: 0
+    Dex: 20
+    Luk: 20
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Angel
+    Element: Holy
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 576
+    AttackMotion: 576
+    DamageMotion: 480
+  - Id: 2038
+    AegisName: VALKYRIE_B
+    Name: Valkyrie Randgris
+    Level: 90
+    Hp: 10000
+    Sp: 15
+    Attack: 300
+    Attack2: 450
+    Defense: 10
+    MagicDefense: 40
+    Agi: 20
+    Vit: 80
+    Int: 0
+    Dex: 80
+    Luk: 20
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Angel
+    Element: Holy
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 576
+    AttackMotion: 576
+    DamageMotion: 480
+  - Id: 2058
+    AegisName: M_MIMIC
+    Name: Mimic
+    Level: 51
+    Hp: 6120
+    Sp: 182
+    Attack: 800
+    Attack2: 950
+    Defense: 10
+    MagicDefense: 40
+    Str: 44
+    Agi: 121
+    Int: 60
+    Dex: 75
+    Luk: 110
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 972
+    AttackMotion: 500
+    DamageMotion: 288
+    Skills:
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+      - Name: ML_AUTOGUARD
+        MaxLevel: 5
+      - Name: MS_BASH
+        MaxLevel: 5
+  - Id: 2059
+    AegisName: M_DISGUISE
+    Name: Disguise
+    Level: 55
+    Hp: 7543
+    Sp: 180
+    Attack: 526
+    Attack2: 693
+    Defense: 18
+    MagicDefense: 29
+    Str: 0
+    Agi: 72
+    Vit: 45
+    Int: 35
+    Dex: 48
+    Luk: 65
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Earth
+    ElementLevel: 4
+    WalkSpeed: 147
+    AttackDelay: 516
+    AttackMotion: 768
+    DamageMotion: 384
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 2
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MER_LEXDIVINA
+        MaxLevel: 3
+  - Id: 2060
+    AegisName: M_ALICE
+    Name: Alice
+    Level: 62
+    Hp: 10000
+    Sp: 221
+    Attack: 700
+    Attack2: 850
+    Defense: 5
+    MagicDefense: 5
+    Str: 64
+    Agi: 64
+    Vit: 42
+    Int: 85
+    Dex: 100
+    Luk: 130
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 200
+    AttackDelay: 502
+    AttackMotion: 1999
+    DamageMotion: 480
+    Skills:
+      - Name: MER_PROVOKE
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: ML_BRANDISH
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+  - Id: 6017
+    AegisName: MER_ARCHER01
+    Name: Mina
+    Level: 20
+    Hp: 256
+    Sp: 200
+    Attack: 170
+    Attack2: 85
+    Defense: 7
+    MagicDefense: 5
+    Agi: 16
+    Vit: 5
+    Dex: 28
+    Luk: 8
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_DOUBLE
+        MaxLevel: 2
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 6018
+    AegisName: MER_ARCHER02
+    Name: Dororu
+    Level: 30
+    Hp: 457
+    Sp: 70
+    Attack: 228
+    Attack2: 114
+    Defense: 11
+    MagicDefense: 7
+    Agi: 18
+    Vit: 8
+    Dex: 40
+    Luk: 11
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_SHOWER
+        MaxLevel: 2
+      - Name: MER_SIGHT
+        MaxLevel: 1
+  - Id: 6019
+    AegisName: MER_ARCHER03
+    Name: Nami
+    Level: 40
+    Hp: 732
+    Sp: 93
+    Attack: 260
+    Attack2: 130
+    Defense: 15
+    MagicDefense: 9
+    Str: 3
+    Agi: 21
+    Vit: 12
+    Int: 4
+    Dex: 52
+    Luk: 17
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_CHARGEARROW
+        MaxLevel: 1
+      - Name: MER_QUICKEN
+        MaxLevel: 1
+  - Id: 6020
+    AegisName: MER_ARCHER04
+    Name: Elfin
+    Level: 50
+    Hp: 1092
+    Sp: 116
+    Attack: 310
+    Attack2: 155
+    Defense: 18
+    MagicDefense: 11
+    Str: 5
+    Agi: 33
+    Vit: 17
+    Int: 6
+    Dex: 60
+    Luk: 23
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_ESTIMATION
+        MaxLevel: 1
+      - Name: MER_MAGNIFICAT
+        MaxLevel: 1
+      - Name: MER_TENDER
+        MaxLevel: 1
+  - Id: 6021
+    AegisName: MER_ARCHER05
+    Name: Clara
+    Level: 60
+    Hp: 2212
+    Sp: 280
+    Attack: 360
+    Attack2: 180
+    Defense: 20
+    MagicDefense: 13
+    Str: 5
+    Agi: 41
+    Vit: 17
+    Int: 12
+    Dex: 75
+    Luk: 30
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_DOUBLE
+        MaxLevel: 5
+      - Name: MER_PROVOKE
+        MaxLevel: 1
+      - Name: MA_REMOVETRAP
+        MaxLevel: 1
+  - Id: 6022
+    AegisName: MER_ARCHER06
+    Name: Dali
+    Level: 70
+    Hp: 3098
+    Sp: 353
+    Attack: 424
+    Attack2: 212
+    Defense: 21
+    MagicDefense: 15
+    Str: 11
+    Agi: 46
+    Vit: 24
+    Int: 22
+    Dex: 83
+    Luk: 37
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_DOUBLE
+        MaxLevel: 7
+      - Name: MA_SKIDTRAP
+        MaxLevel: 3
+      - Name: MER_DECAGI
+        MaxLevel: 1
+  - Id: 6023
+    AegisName: MER_ARCHER07
+    Name: Karaya
+    Level: 80
+    Hp: 4051
+    Sp: 415
+    Attack: 468
+    Attack2: 234
+    Defense: 22
+    MagicDefense: 16
+    Str: 14
+    Agi: 55
+    Vit: 27
+    Int: 26
+    Dex: 91
+    Luk: 44
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_SHOWER
+        MaxLevel: 10
+      - Name: MER_MENTALCURE
+        MaxLevel: 1
+      - Name: MA_FREEZINGTRAP
+        MaxLevel: 2
+  - Id: 6024
+    AegisName: MER_ARCHER08
+    Name: Hiyori
+    Level: 90
+    Hp: 5039
+    Sp: 469
+    Attack: 482
+    Attack2: 241
+    Defense: 24
+    MagicDefense: 18
+    Str: 19
+    Agi: 65
+    Vit: 27
+    Int: 27
+    Dex: 103
+    Luk: 49
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 2
+      - Name: MER_PROVOKE
+        MaxLevel: 3
+      - Name: MA_SANDMAN
+        MaxLevel: 3
+  - Id: 6025
+    AegisName: MER_ARCHER09
+    Name: Kero
+    Level: 95
+    Hp: 5572
+    Sp: 499
+    Attack: 500
+    Attack2: 250
+    Defense: 25
+    MagicDefense: 20
+    Str: 20
+    Agi: 71
+    Vit: 27
+    Int: 28
+    Dex: 110
+    Luk: 51
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_DOUBLE
+        MaxLevel: 10
+      - Name: MA_CHARGEARROW
+        MaxLevel: 1
+      - Name: MA_LANDMINE
+        MaxLevel: 5
+  - Id: 6026
+    AegisName: MER_ARCHER10
+    Name: Sukye
+    Level: 99
+    Hp: 7381
+    Sp: 642
+    Attack: 816
+    Attack2: 308
+    Defense: 49
+    MagicDefense: 49
+    Str: 21
+    Agi: 83
+    Vit: 27
+    Int: 28
+    Dex: 123
+    Luk: 52
+    AttackRange: 10
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MA_CHARGEARROW
+        MaxLevel: 1
+      - Name: MA_SHARPSHOOTING
+        MaxLevel: 5
+      - Name: MER_QUICKEN
+        MaxLevel: 5
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 6027
+    AegisName: MER_LANCER01
+    Name: Rodin
+    Level: 22
+    Hp: 2071
+    Sp: 100
+    Attack: 168
+    Attack2: 84
+    Defense: 30
+    MagicDefense: 1
+    Str: 27
+    Vit: 4
+    Dex: 30
+    Luk: 2
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_PIERCE
+        MaxLevel: 1
+      - Name: MER_REGAIN
+        MaxLevel: 1
+  - Id: 6028
+    AegisName: MER_LANCER02
+    Name: Lancer
+    Level: 34
+    Hp: 2523
+    Sp: 131
+    Attack: 208
+    Attack2: 104
+    Defense: 33
+    MagicDefense: 1
+    Str: 37
+    Vit: 4
+    Dex: 37
+    Luk: 4
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_BRANDISH
+        MaxLevel: 2
+      - Name: MER_LEXDIVINA
+        MaxLevel: 1
+  - Id: 6029
+    AegisName: MER_LANCER03
+    Name: Nathan
+    Level: 41
+    Hp: 3397
+    Sp: 161
+    Attack: 248
+    Attack2: 124
+    Defense: 36
+    MagicDefense: 5
+    Str: 45
+    Vit: 22
+    Dex: 40
+    Luk: 6
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_PIERCE
+        MaxLevel: 2
+      - Name: ML_DEVOTION
+        MaxLevel: 1
+      - Name: MER_RECUPERATE
+        MaxLevel: 1
+  - Id: 6030
+    AegisName: MER_LANCER04
+    Name: Roan
+    Level: 55
+    Hp: 4580
+    Sp: 191
+    Attack: 300
+    Attack2: 150
+    Defense: 39
+    MagicDefense: 5
+    Str: 55
+    Agi: 2
+    Vit: 35
+    Dex: 50
+    Luk: 8
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_DEFENDER
+        MaxLevel: 1
+      - Name: MER_CRASH
+        MaxLevel: 4
+  - Id: 6031
+    AegisName: MER_LANCER05
+    Name: Orizaro
+    Level: 60
+    Hp: 5899
+    Sp: 221
+    Attack: 350
+    Attack2: 160
+    Defense: 42
+    MagicDefense: 10
+    Str: 65
+    Agi: 2
+    Vit: 39
+    Dex: 60
+    Luk: 10
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_PIERCE
+        MaxLevel: 5
+      - Name: ML_AUTOGUARD
+        MaxLevel: 3
+  - Id: 6032
+    AegisName: MER_LANCER06
+    Name: Thyla
+    Level: 72
+    Hp: 7874
+    Sp: 252
+    Attack: 370
+    Attack2: 185
+    Defense: 46
+    MagicDefense: 10
+    Str: 75
+    Agi: 3
+    Vit: 15
+    Int: 77
+    Dex: 61
+    Luk: 12
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 2
+      - Name: ML_BRANDISH
+        MaxLevel: 5
+  - Id: 6033
+    AegisName: MER_LANCER07
+    Name: Ben
+    Level: 81
+    Hp: 10260
+    Sp: 330
+    Attack: 380
+    Attack2: 190
+    Defense: 50
+    MagicDefense: 15
+    Str: 85
+    Agi: 3
+    Vit: 63
+    Int: 20
+    Dex: 61
+    Luk: 14
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_DEVOTION
+        MaxLevel: 1
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 6034
+    AegisName: MER_LANCER08
+    Name: Pinaka
+    Level: 90
+    Hp: 13167
+    Sp: 366
+    Attack: 400
+    Attack2: 200
+    Defense: 55
+    MagicDefense: 20
+    Str: 95
+    Agi: 20
+    Vit: 74
+    Int: 20
+    Dex: 63
+    Luk: 16
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_SCAPEGOAT
+        MaxLevel: 1
+      - Name: ML_PIERCE
+        MaxLevel: 10
+      - Name: MER_PROVOKE
+        MaxLevel: 5
+  - Id: 6035
+    AegisName: MER_LANCER09
+    Name: Kuhlmann
+    Level: 95
+    Hp: 14648
+    Sp: 398
+    Attack: 440
+    Attack2: 220
+    Defense: 60
+    MagicDefense: 25
+    Str: 100
+    Agi: 25
+    Vit: 77
+    Int: 25
+    Dex: 63
+    Luk: 18
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: ML_BRANDISH
+        MaxLevel: 10
+      - Name: ML_AUTOGUARD
+        MaxLevel: 7
+      - Name: ML_DEFENDER
+        MaxLevel: 3
+  - Id: 6036
+    AegisName: MER_LANCER10
+    Name: Roux
+    Level: 99
+    Hp: 18000
+    Sp: 413
+    Attack: 700
+    Attack2: 250
+    Defense: 70
+    MagicDefense: 30
+    Str: 120
+    Agi: 30
+    Vit: 90
+    Int: 30
+    Dex: 70
+    Luk: 30
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 5
+      - Name: ML_AUTOGUARD
+        MaxLevel: 10
+      - Name: ML_DEVOTION
+        MaxLevel: 3
+      - Name: ML_SPIRALPIERCE
+        MaxLevel: 5
+  - Id: 6037
+    AegisName: MER_SWORDMAN01
+    Name: David
+    Level: 20
+    Hp: 502
+    Sp: 70
+    Attack: 174
+    Attack2: 87
+    Defense: 26
+    Str: 21
+    Agi: 27
+    Vit: 5
+    Dex: 30
+    Luk: 3
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 1
+      - Name: MER_DECAGI
+        MaxLevel: 1
+  - Id: 6038
+    AegisName: MER_SWORDMAN02
+    Name: Ellen
+    Level: 30
+    Hp: 979
+    Sp: 99
+    Attack: 258
+    Attack2: 129
+    Defense: 31
+    Str: 26
+    Agi: 38
+    Vit: 6
+    Dex: 35
+    Luk: 3
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_PROVOKE
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 3
+  - Id: 6039
+    AegisName: MER_SWORDMAN03
+    Name: Luise
+    Level: 40
+    Hp: 1555
+    Sp: 134
+    Attack: 326
+    Attack2: 163
+    Defense: 36
+    MagicDefense: 3
+    Str: 31
+    Agi: 45
+    Vit: 7
+    Int: 6
+    Dex: 40
+    Luk: 6
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 700
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 1
+      - Name: MER_BENEDICTION
+        MaxLevel: 1
+  - Id: 6040
+    AegisName: MER_SWORDMAN04
+    Name: Frank
+    Level: 50
+    Hp: 2397
+    Sp: 162
+    Attack: 382
+    Attack2: 191
+    Defense: 39
+    MagicDefense: 5
+    Str: 37
+    Agi: 59
+    Vit: 8
+    Int: 6
+    Dex: 40
+    Luk: 6
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_CRASH
+        MaxLevel: 1
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+  - Id: 6041
+    AegisName: MER_SWORDMAN05
+    Name: Ryan
+    Level: 60
+    Hp: 3387
+    Sp: 195
+    Attack: 406
+    Attack2: 203
+    Defense: 42
+    MagicDefense: 7
+    Str: 48
+    Agi: 68
+    Vit: 9
+    Int: 6
+    Dex: 45
+    Luk: 9
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 5
+      - Name: MER_CRASH
+        MaxLevel: 4
+      - Name: MER_BENEDICTION
+        MaxLevel: 1
+  - Id: 6042
+    AegisName: MER_SWORDMAN06
+    Name: Paolo
+    Level: 70
+    Hp: 4495
+    Sp: 241
+    Attack: 436
+    Attack2: 218
+    Defense: 45
+    MagicDefense: 12
+    Str: 53
+    Agi: 79
+    Vit: 10
+    Int: 12
+    Dex: 50
+    Luk: 9
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 575
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 5
+      - Name: MER_ESTIMATION
+        MaxLevel: 1
+      - Name: MER_DECAGI
+        MaxLevel: 3
+  - Id: 6043
+    AegisName: MER_SWORDMAN07
+    Name: Jens
+    Level: 80
+    Hp: 5889
+    Sp: 279
+    Attack: 468
+    Attack2: 234
+    Defense: 47
+    MagicDefense: 15
+    Str: 59
+    Agi: 88
+    Vit: 11
+    Int: 12
+    Dex: 55
+    Luk: 12
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 10
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+      - Name: MER_SCAPEGOAT
+        MaxLevel: 1
+  - Id: 6044
+    AegisName: MER_SWORDMAN08
+    Name: Thierry
+    Level: 90
+    Hp: 7520
+    Sp: 325
+    Attack: 500
+    Attack2: 250
+    Defense: 49
+    MagicDefense: 18
+    Str: 70
+    Agi: 95
+    Vit: 12
+    Int: 18
+    Dex: 60
+    Luk: 15
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 10
+      - Name: MS_BOWLINGBASH
+        MaxLevel: 5
+      - Name: MER_COMPRESS
+        MaxLevel: 1
+      - Name: MS_PARRYING
+        MaxLevel: 4
+  - Id: 6045
+    AegisName: MER_SWORDMAN09
+    Name: Steven
+    Level: 95
+    Hp: 9052
+    Sp: 348
+    Attack: 524
+    Attack2: 262
+    Defense: 51
+    MagicDefense: 22
+    Str: 80
+    Agi: 95
+    Vit: 18
+    Int: 18
+    Dex: 60
+    Luk: 15
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MS_BOWLINGBASH
+        MaxLevel: 8
+      - Name: MER_CRASH
+        MaxLevel: 3
+      - Name: MS_REFLECTSHIELD
+        MaxLevel: 5
+  - Id: 6046
+    AegisName: MER_SWORDMAN10
+    Name: Wayne
+    Level: 99
+    Hp: 12355
+    Sp: 451
+    Attack: 760
+    Attack2: 280
+    Defense: 64
+    MagicDefense: 30
+    Str: 99
+    Agi: 105
+    Vit: 30
+    Int: 30
+    Dex: 60
+    Luk: 30
+    AttackRange: 2
+    SkillRange: 10
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 150
+    AttackDelay: 450
+    AttackMotion: 432
+    DamageMotion: 300
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 10
+      - Name: MS_BOWLINGBASH
+        MaxLevel: 10
+      - Name: MS_BASH
+        MaxLevel: 10
+      - Name: MS_BERSERK
+        MaxLevel: 1
+
+Footer:
+  Imports:
+  - Path: db/pre-re/mercenary_db.yml
+    Mode: Prerenewal
+  - Path: db/re/mercenary_db.yml
+    Mode: Renewal
+  - Path: db/import/mercenary_db.yml

+ 0 - 219
db/mercenary_skill_db.txt

@@ -1,219 +0,0 @@
-// Mercenary Skill Database
-//
-// Structure of Database:
-// MercenryID,SkillID,SkillLevel
-
-// Archer Mercenaries Level 1-10.
-// MER_ARCHER01
-6017,8207,2	//MA_DOUBLE
-6017,8233,1	//MER_AUTOBERSERK
-// MER_ARCHER02
-6018,8208,2	//MA_SHOWER
-6018,8224,1	//MER_SIGHT
-// MER_ARCHER03
-6019,8214,1	//MA_CHARGEARROW
-6019,8223,1	//MER_QUICKEN
-// MER_ARCHER04
-6020,8237,1	//MER_ESTIMATION
-6020,8222,1	//MER_MAGNIFICAT
-6020,8227,1	//MER_TENDER
-// MER_ARCHER05
-6021,8207,5	//MA_DOUBLE
-6021,8232,1	//MER_PROVOKE
-6021,8213,1	//MA_REMOVETRAP
-// MER_ARCHER06
-6022,8207,7	//MA_DOUBLE
-6022,8209,3	//MA_SKIDTRAP
-6022,8234,1	//MER_DECAGI
-// MER_ARCHER07
-6023,8208,10	//MA_SHOWER
-6023,8230,1	//MER_MENTALCURE
-6023,8212,2	//MA_FREEZINGTRAP
-// MER_ARCHER08
-6024,8223,2	//MER_QUICKEN
-6024,8232,3	//MER_PROVOKE
-6024,8211,3	//MA_SANDMAN
-// MER_ARCHER09
-6025,8207,10	//MA_DOUBLE
-6025,8214,1	//MA_CHARGEARROW
-6025,8210,5	//MA_LANDMINE
-// MER_ARCHER10
-6026,8214,1	//MA_CHARGEARROW
-6026,8215,5	//MA_SHARPSHOOTING
-6026,8223,5	//MER_QUICKEN
-6026,8233,1	//MER_AUTOBERSERK
-
-// Lancer Mercenaries Level 1-10.
-// MER_LANCER01
-6027,8216,1	//ML_PIERCE
-6027,8226,1	//MER_REGAIN
-// MER_LANCER02
-6028,8217,2	//ML_BRANDISH
-6028,8236,1	//MER_LEXDIVINA
-// MER_LANCER03
-6029,8216,2	//ML_PIERCE
-6029,8221,1	//ML_DEVOTION
-6029,8229,1	//MER_RECUPERATE
-// MER_LANCER04
-6030,8219,1	//ML_DEFENDER
-6030,8225,4	//MER_CRASH
-// MER_LANCER05
-6031,8216,5	//ML_PIERCE
-6031,8220,3	//ML_AUTOGUARD
-// MER_LANCER06
-6032,8223,2	//MER_QUICKEN
-6032,8217,5	//ML_BRANDISH
-// MER_LANCER07
-6033,8221,1	//ML_DEVOTION
-6033,8233,1	//MER_AUTOBERSERK
-// MER_LANCER08
-6034,8235,1	//MER_SCAPEGOAT
-6034,8216,10	//ML_PIERCE
-6034,8232,5	//MER_PROVOKE
-// MER_LANCER09
-6035,8217,10	//ML_BRANDISH
-6035,8220,7	//ML_AUTOGUARD
-6035,8219,3	//ML_DEFENDER
-// MER_LANCER10
-6036,8223,5	//MER_QUICKEN
-6036,8220,10	//ML_AUTOGUARD
-6036,8221,3	//ML_DEVOTION
-6036,8218,5	//ML_SPIRALPIERCE
-
-// Swordman Mercenaries Level 1-10.
-// MER_SWORDMAN01
-6037,8201,1	//MS_BASH
-6037,8234,1	//MER_DECAGI
-// MER_SWORDMAN02
-6038,8232,5	//MER_PROVOKE
-6038,8202,3	//MS_MAGNUM
-// MER_SWORDMAN03
-6039,8223,1	//MER_QUICKEN
-6039,8228,1	//MER_BENEDICTION
-// MER_SWORDMAN04
-6040,8225,1	//MER_CRASH
-6040,8202,5	//MS_MAGNUM
-// MER_SWORDMAN05
-6041,8201,5	//MS_BASH
-6041,8225,4	//MER_CRASH
-6041,8228,1	//MER_BENEDICTION
-// MER_SWORDMAN06
-6042,8223,5	//MER_QUICKEN
-6042,8237,1	//MER_ESTIMATION
-6042,8234,3	//MER_DECAGI
-// MER_SWORDMAN07
-6043,8201,10	//MS_BASH
-6043,8233,1	//MER_AUTOBERSERK
-6043,8235,1	//MER_SCAPEGOAT
-// MER_SWORDMAN08
-6044,8223,10	//MER_QUICKEN
-6044,8203,5	//MS_BOWLINGABASH
-6044,8231,1	//MER_COMPRESS
-6044,8204,4	//MS_PARRYING
-// MER_SWORDMAN09
-6045,8203,8	//MS_BOWLINGABASH
-6045,8225,3	//MER_CRASH
-6045,8205,5	//MS_REFLECTSHIELD
-// MER_SWORDMAN10
-6046,8223,10	//MER_QUICKEN
-6046,8203,10	//MS_BOWLINGABASH
-6046,8201,10	//MS_BASH
-6046,8206,1	//MS_BERSERK
-
-// Monster Mercenaries
-// MIMIC
-1191,8233,1	//MER_AUTOBERSERK
-1191,8220,5	//ML_AUTOGUARD
-1191,8201,5	//MS_BASH
-// DISGUISE
-1506,8223,2	//MER_QUICKEN
-1506,8225,5	//MER_CRASH
-1506,8236,3	//MER_LEXDIVINA
-// ALICE
-1275,8232,5	//MER_PROVOKE
-1275,8202,5	//MS_MAGNUM
-1275,8217,5	//ML_BRANDISH
-1275,8226,1	//MER_REGAIN
-// M_WILD_ROSE
-1965,8201,5	//MS_BASH
-// M_DOPPELGANGER
-1966,8223,5	//MER_QUICKEN
-1966,8201,5	//MS_BASH
-1966,8221,3	//ML_DEVOTION
-// M_YGNIZEM
-1967,8223,5	//MER_QUICKEN
-1967,8201,5	//MS_BASH
-1967,8221,3	//ML_DEVOTION
-// M_GAMEMASTER
-2000,8226,1	//MER_REGAIN
-2000,8227,1	//MER_TENDER
-2000,8238,5	//MER_KYRIE
-2000,8239,5	//MER_BLESSING
-2000,8240,5	//MER_INCAGI
-// F_GAMEMASTER
-2001,8226,1	//MER_REGAIN
-2001,8227,1	//MER_TENDER
-2001,8238,5	//MER_KYRIE
-2001,8239,5	//MER_BLESSING
-2001,8240,5	//MER_INCAGI
-// M_MIMIC
-2058,8233,1	//MER_AUTOBERSERK
-2058,8220,5	//ML_AUTOGUARD
-2058,8201,5	//MS_BASH
-// M_DISGUISE
-2059,8223,2	//MER_QUICKEN
-2059,8225,5	//MER_CRASH
-2059,8236,3	//MER_LEXDIVINA
-// M_ALICE
-2060,8232,5	//MER_PROVOKE
-2060,8202,5	//MS_MAGNUM
-2060,8217,5	//ML_BRANDISH
-2060,8226,1	//MER_REGAIN
-// M_WANDER_MAN
-2213,8223,10	//MER_QUICKEN
-2213,8204,4	//MS_PARRYING
-2213,8233,1	//MER_AUTOBERSERK
-2213,8203,5	//MS_BOWLINGBASH
-// M_WICKED_NYMPH
-2214,8236,3	//MER_LEXDIVINA
-2214,8239,5	//MER_BLESSING
-2214,8227,1	//MER_TENDER
-2214,8222,1	//MER_MAGNIFICAT
-// M_KASA
-2215,8225,5	//MER_CRASH
-2215,8202,5	//MS_MAGNUM
-2215,8226,1	//MER_REGAIN
-2215,8233,1	//MER_AUTOBERSERK
-// M_SALAMANDER
-2216,8201,5	//MS_BASH
-2216,8217,5	//ML_BRANDISH
-2216,8233,1	//MER_AUTOBERSERK
-2216,8228,1	//MER_BENEDICTION
-// M_TEDDY_BEAR
-2217,8221,3	//ML_DEVOTION
-2217,8226,1	//MER_REGAIN
-2217,8219,1	//ML_DEFENDER
-2217,8235,1	//MER_SCAPEGOAT
-// M_BAPHOMET_
-2325,8201,5	//MS_BASH
-2325,8223,10	//MER_QUICKEN
-// M_GALAPAGO
-2326,8239,5	//MER_BLESSING
-//2326,28,5	//AL_HEAL
-// MER_DIABOLIC
-2342,8225,5	//MER_CRASH
-2342,8202,5	//MS_MAGNUM
-2342,8226,1	//MER_REGAIN
-2342,8233,1	//MER_AUTOBERSERK
-// MER_ZHERLTHSH
-2345,8225,5	//MER_CRASH
-2345,8202,5	//MS_MAGNUM
-2345,8226,1	//MER_REGAIN
-2345,8233,1	//MER_AUTOBERSERK
-// MER_EDDGA
-2347,8225,5	//MER_CRASH
-2347,8202,5	//MS_MAGNUM
-2347,8226,1	//MER_REGAIN
-2347,8233,1	//MER_AUTOBERSERK
-// M_LOKI
-2937,8241,1	//MER_INVINCIBLEOFF2

+ 59 - 0
db/pre-re/mercenary_db.yml

@@ -0,0 +1,59 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2021 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/>.
+#
+###########################################################################
+# Mercenary Database
+###########################################################################
+#
+# Mercenary Settings
+#
+###########################################################################
+# - Id                      Mercenary ID.
+#   AegisName               Server name to reference the mercenary in scripts and lookups, should use no spaces.
+#   Name                    Name in English.
+#   Level                   Level. (Default: 1)
+#   Hp                      Total HP. (Default: 1)
+#   Sp                      Total SP. (Default: 1)
+#   Attack                  Minimum attack. (Default: 0)
+#   Attack2                 Maximum attack. (Default: 0)
+#   Defense                 Physical defense of the mercenary, reduces melee and ranged physical attack/skill damage. (Default: 0)
+#   MagicDefense            Magic defense of the mercenary, reduces magical skill damage. (Default: 0)
+#   Str                     Strength which affects attack. (Default: 1)
+#   Agi                     Agility which affects flee. (Default: 1)
+#   Vit                     Vitality which affects defense. (Default: 1)
+#   Int                     Intelligence which affects magic attack. (Default: 1)
+#   Dex                     Dexterity which affects hit rate. (Default: 1)
+#   Luk                     Luck which affects perfect dodge/lucky flee/perfect flee/lucky dodge rate. (Default: 1)
+#   AttackRange             Attack range. (Default: 0)
+#   SkillRange              Skill cast range. (Default: 0)
+#   ChaseRange              Chase range. (Default: 0)
+#   Size                    Size. (Default: Small)
+#   Race                    Race. (Default: Formless)
+#   Element                 Element. (Default: Neutral)
+#   ElementLevel            Level of element. (Default: 1)
+#   WalkSpeed               Walk speed. (Default: DEFAULT_WALK_SPEED)
+#   AttackDelay             Attack speed. (Default: 4000)
+#   AttackMotion            Attack animation speed. (Default: 2000)
+#   DamageMotion            Damage animation speed. (Default: 0)
+#   Skills:                 List of mercenary skills. (Optional)
+#     - Name                Skill name.
+#       MaxLevel            Max skill level.
+###########################################################################
+
+Header:
+  Type: MERCENARY_DB
+  Version: 1

+ 613 - 0
db/re/mercenary_db.yml

@@ -0,0 +1,613 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2021 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/>.
+#
+###########################################################################
+# Mercenary Database
+###########################################################################
+#
+# Mercenary Settings
+#
+###########################################################################
+# - Id                      Mercenary ID.
+#   AegisName               Server name to reference the mercenary in scripts and lookups, should use no spaces.
+#   Name                    Name in English.
+#   Level                   Level. (Default: 1)
+#   Hp                      Total HP. (Default: 1)
+#   Sp                      Total SP. (Default: 1)
+#   Attack                  Minimum attack. (Default: 0)
+#   Attack2                 Maximum attack. (Default: 0)
+#   Defense                 Physical defense of the mercenary, reduces melee and ranged physical attack/skill damage. (Default: 0)
+#   MagicDefense            Magic defense of the mercenary, reduces magical skill damage. (Default: 0)
+#   Str                     Strength which affects attack. (Default: 1)
+#   Agi                     Agility which affects flee. (Default: 1)
+#   Vit                     Vitality which affects defense. (Default: 1)
+#   Int                     Intelligence which affects magic attack. (Default: 1)
+#   Dex                     Dexterity which affects hit rate. (Default: 1)
+#   Luk                     Luck which affects perfect dodge/lucky flee/perfect flee/lucky dodge rate. (Default: 1)
+#   AttackRange             Attack range. (Default: 0)
+#   SkillRange              Skill cast range. (Default: 0)
+#   ChaseRange              Chase range. (Default: 0)
+#   Size                    Size. (Default: Small)
+#   Race                    Race. (Default: Formless)
+#   Element                 Element. (Default: Neutral)
+#   ElementLevel            Level of element. (Default: 1)
+#   WalkSpeed               Walk speed. (Default: DEFAULT_WALK_SPEED)
+#   AttackDelay             Attack speed. (Default: 4000)
+#   AttackMotion            Attack animation speed. (Default: 2000)
+#   DamageMotion            Damage animation speed. (Default: 0)
+#   Skills:                 List of mercenary skills. (Optional)
+#     - Name                Skill name.
+#       MaxLevel            Max skill level.
+###########################################################################
+
+Header:
+  Type: MERCENARY_DB
+  Version: 1
+
+Body:
+  - Id: 2213
+    AegisName: M_WANDER_MAN
+    Name: Wander Man
+    Level: 81
+    Hp: 8614
+    Sp: 220
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 20
+    Str: 80
+    Agi: 110
+    Vit: 63
+    Int: 51
+    Dex: 85
+    Luk: 90
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Wind
+    ElementLevel: 1
+    WalkSpeed: 100
+    AttackDelay: 672
+    AttackMotion: 500
+    DamageMotion: 192
+    Skills:
+      - Name: MER_QUICKEN
+        MaxLevel: 10
+      - Name: MS_PARRYING
+        MaxLevel: 4
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+      - Name: MS_BOWLINGBASH
+        MaxLevel: 5
+  - Id: 2214
+    AegisName: M_WICKED_NYMPH
+    Name: Wicked Nymph
+    Level: 85
+    Hp: 6157
+    Sp: 256
+    Attack: 420
+    Attack2: 620
+    Defense: 30
+    MagicDefense: 45
+    Str: 40
+    Agi: 50
+    Vit: 40
+    Int: 92
+    Dex: 60
+    Luk: 110
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Dark
+    ElementLevel: 3
+    WalkSpeed: 200
+    AttackDelay: 637
+    AttackMotion: 1008
+    DamageMotion: 360
+    Skills:
+      - Name: MER_LEXDIVINA
+        MaxLevel: 3
+      - Name: MER_BLESSING
+        MaxLevel: 5
+      - Name: MER_TENDER
+        MaxLevel: 1
+      - Name: MER_MAGNIFICAT
+        MaxLevel: 1
+  - Id: 2215
+    AegisName: M_KASA
+    Name: Kasa
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Large
+    Element: Fire
+    ElementLevel: 3
+    WalkSpeed: 150
+    AttackDelay: 800
+    AttackMotion: 600
+    DamageMotion: 288
+    Skills:
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 2216
+    AegisName: M_SALAMANDER
+    Name: Salamander
+    Level: 87
+    Hp: 9517
+    Sp: 260
+    Attack: 900
+    Attack2: 1100
+    Defense: 60
+    MagicDefense: 68
+    Str: 90
+    Agi: 80
+    Vit: 65
+    Int: 45
+    Dex: 87
+    Luk: 95
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Large
+    Element: Fire
+    ElementLevel: 3
+    WalkSpeed: 160
+    AttackDelay: 140
+    AttackMotion: 384
+    DamageMotion: 288
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 5
+      - Name: ML_BRANDISH
+        MaxLevel: 5
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+      - Name: MER_BENEDICTION
+        MaxLevel: 1
+  - Id: 2217
+    AegisName: M_TEDDY_BEAR
+    Name: Teddy Bear
+    Level: 85
+    Hp: 14493
+    Sp: 243
+    Attack: 600
+    Attack2: 800
+    Defense: 100
+    MagicDefense: 70
+    Str: 60
+    Agi: 20
+    Vit: 85
+    Int: 50
+    Dex: 75
+    Luk: 130
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 200
+    AttackDelay: 512
+    AttackMotion: 780
+    DamageMotion: 504
+    Skills:
+      - Name: ML_DEVOTION
+        MaxLevel: 3
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: ML_DEFENDER
+        MaxLevel: 1
+      - Name: MER_SCAPEGOAT
+        MaxLevel: 1
+  - Id: 2325
+    AegisName: M_BAPHOMET_
+    Name: Baphomet
+    Level: 57
+    Hp: 7510
+    Sp: 204
+    Attack: 810
+    Attack2: 955
+    Defense: 70
+    MagicDefense: 40
+    Str: 52
+    Agi: 60
+    Vit: 36
+    Int: 17
+    Dex: 57
+    Luk: 25
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 1
+    WalkSpeed: 100
+    AttackDelay: 868
+    AttackMotion: 480
+    DamageMotion: 120
+    Skills:
+      - Name: MS_BASH
+        MaxLevel: 5
+      - Name: MER_QUICKEN
+        MaxLevel: 10
+  - Id: 2326
+    AegisName: M_GALAPAGO
+    Name: Galapago
+    Level: 45
+    Hp: 7513
+    Sp: 201
+    Attack: 760
+    Attack2: 915
+    Defense: 70
+    MagicDefense: 40
+    Str: 30
+    Agi: 28
+    Vit: 29
+    Int: 18
+    Dex: 30
+    Luk: 16
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Brute
+    Element: Earth
+    ElementLevel: 1
+    WalkSpeed: 165
+    AttackDelay: 1430
+    AttackMotion: 1080
+    DamageMotion: 1080
+    Skills:
+      - Name: MER_BLESSING
+        MaxLevel: 5
+  - Id: 2342
+    AegisName: MER_DIABOLIC
+    Name: Diabolic
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+    Skills:
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 2344
+    AegisName: MER_WISH_MAIDEN
+    Name: Wish Maiden
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2345
+    AegisName: MER_ZHERLTHSH
+    Name: Zherlthsh
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+    Skills:
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 2346
+    AegisName: MER_KTULLANUX
+    Name: Ktullanux
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2347
+    AegisName: MER_EDDGA
+    Name: Eddga
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+    Skills:
+      - Name: MER_CRASH
+        MaxLevel: 5
+      - Name: MS_MAGNUM
+        MaxLevel: 5
+      - Name: MER_REGAIN
+        MaxLevel: 1
+      - Name: MER_AUTOBERSERK
+        MaxLevel: 1
+  - Id: 2348
+    AegisName: MER_CIVIL_SERVANT
+    Name: Civil Servant
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2349
+    AegisName: MER_LOLI_RURI
+    Name: Loli Ruri
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2350
+    AegisName: MER_SEDORA
+    Name: Sedora
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2351
+    AegisName: MER_CHEPET
+    Name: Chepet
+    Level: 83
+    Hp: 9815
+    Sp: 234
+    Attack: 1100
+    Attack2: 1300
+    Defense: 60
+    MagicDefense: 60
+    Str: 85
+    Agi: 90
+    Vit: 71
+    Int: 43
+    Dex: 85
+    Luk: 105
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Race: Demon
+    Element: Dark
+    ElementLevel: 2
+    WalkSpeed: 150
+    AttackDelay: 1080
+    AttackMotion: 780
+    DamageMotion: 180
+  - Id: 2378
+    AegisName: MER_ANTLER_SCARABA
+    Name: Antler Scaraba
+    Level: 136
+    Hp: 30000
+    Attack: 1418
+    Attack2: 1828
+    Defense: 155
+    MagicDefense: 102
+    Str: 23
+    Agi: 99
+    Vit: 59
+    Int: 129
+    Dex: 137
+    Luk: 45
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Insect
+    Element: Earth
+    ElementLevel: 2
+    WalkSpeed: 200
+    AttackDelay: 504
+    AttackMotion: 624
+    DamageMotion: 360
+  - Id: 2937
+    AegisName: M_LOKI
+    Name: Loki's Shadow
+    Level: 145
+    Hp: 1215600
+    Attack: 1835
+    Attack2: 2279
+    Defense: 15
+    MagicDefense: 89
+    Str: 76
+    Agi: 66
+    Vit: 90
+    Int: 55
+    Dex: 189
+    Luk: 22
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 175
+    AttackDelay: 800
+    AttackMotion: 750
+    DamageMotion: 300
+    Skills:
+      - Name: MER_INVINCIBLEOFF2
+        MaxLevel: 1

+ 193 - 193
db/re/mob_db.yml

@@ -40946,32 +40946,32 @@ Body:
     Drops:
       - Item: Gold_Tulip
         Rate: 10000
-#  - Id: 2034
-#    AegisName: M_DESERT_WOLF_B
-#    Name: Baby Desert Wolf
-#    Level: 9
-#    Hp: 164
-#    Sp: 15
-#    Attack: 500
-#    Attack2: 600
-#    Agi: 9
-#    Vit: 9
-#    Int: 5
-#    Dex: 40
-#    Luk: 40
-#    AttackRange: 1
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Small
-#    Race: Brute
-#    Element: Fire
-#    ElementLevel: 1
-#    WalkSpeed: 100
-#    AttackDelay: 1600
-#    AttackMotion: 900
-#    DamageMotion: 240
-#    Modes:
-#      NoRandomWalk: true
+  - Id: 2034
+    AegisName: M_DESERT_WOLF_B
+    Name: Baby Desert Wolf
+    Level: 9
+    Hp: 164
+    Sp: 15
+    Attack: 500
+    Attack2: 600
+    Agi: 9
+    Vit: 9
+    Int: 5
+    Dex: 40
+    Luk: 40
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Small
+    Race: Brute
+    Element: Fire
+    ElementLevel: 1
+    WalkSpeed: 100
+    AttackDelay: 1600
+    AttackMotion: 900
+    DamageMotion: 240
+    Modes:
+      NoRandomWalk: true
   - Id: 2035
     AegisName: NIHILITY_ZEM
     Name: Nihility Zem
@@ -41026,60 +41026,60 @@ Body:
       - Item: Broken_Horn_Pipe
         Rate: 2500
         StealProtected: true
-#  - Id: 2037
-#    AegisName: VALKYRIE_A
-#    Name: Valkyrie Randgris
-#    Level: 90
-#    Hp: 5500
-#    Sp: 15
-#    Attack: 10
-#    Attack2: 160
-#    Defense: 16
-#    MagicDefense: 20
-#    Agi: 20
-#    Vit: 40
-#    Dex: 20
-#    Luk: 20
-#    AttackRange: 1
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Angel
-#    Element: Holy
-#    ElementLevel: 3
-#    WalkSpeed: 100
-#    AttackDelay: 576
-#    AttackMotion: 576
-#    DamageMotion: 480
-#    Modes:
-#      NoRandomWalk: true
-#  - Id: 2038
-#    AegisName: VALKYRIE_B
-#    Name: Valkyrie Randgris
-#    Level: 90
-#    Hp: 10500
-#    Sp: 15
-#    Attack: 300
-#    Attack2: 450
-#    Defense: 16
-#    MagicDefense: 40
-#    Agi: 20
-#    Vit: 80
-#    Dex: 80
-#    Luk: 20
-#    AttackRange: 1
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Angel
-#    Element: Holy
-#    ElementLevel: 3
-#    WalkSpeed: 100
-#    AttackDelay: 576
-#    AttackMotion: 576
-#    DamageMotion: 480
-#    Modes:
-#      NoRandomWalk: true
+  - Id: 2037
+    AegisName: VALKYRIE_A
+    Name: Valkyrie Randgris
+    Level: 90
+    Hp: 5500
+    Sp: 15
+    Attack: 10
+    Attack2: 160
+    Defense: 16
+    MagicDefense: 20
+    Agi: 20
+    Vit: 40
+    Dex: 20
+    Luk: 20
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Angel
+    Element: Holy
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 576
+    AttackMotion: 576
+    DamageMotion: 480
+    Modes:
+      NoRandomWalk: true
+  - Id: 2038
+    AegisName: VALKYRIE_B
+    Name: Valkyrie Randgris
+    Level: 90
+    Hp: 10500
+    Sp: 15
+    Attack: 300
+    Attack2: 450
+    Defense: 16
+    MagicDefense: 40
+    Agi: 20
+    Vit: 80
+    Dex: 80
+    Luk: 20
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Angel
+    Element: Holy
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 576
+    AttackMotion: 576
+    DamageMotion: 480
+    Modes:
+      NoRandomWalk: true
   - Id: 2039
     AegisName: EXECUTIONER_R
     Name: Executioner
@@ -41661,91 +41661,91 @@ Body:
     AttackMotion: 500
     DamageMotion: 1000
     Ai: 09
-#  - Id: 2058
-#    AegisName: M_MIMIC
-#    Name: Mimic
-#    Level: 51
-#    Hp: 6120
-#    Sp: 182
-#    Attack: 800
-#    Attack2: 950
-#    Defense: 10
-#    MagicDefense: 40
-#    Str: 44
-#    Agi: 121
-#    Int: 60
-#    Dex: 75
-#    Luk: 110
-#    AttackRange: 1
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Formless
-#    Element: Neutral
-#    ElementLevel: 3
-#    WalkSpeed: 100
-#    AttackDelay: 972
-#    AttackMotion: 500
-#    DamageMotion: 288
-#    Modes:
-#      NoRandomWalk: true
-#  - Id: 2059
-#    AegisName: M_DISGUISE
-#    Name: Disguise
-#    Level: 55
-#    Hp: 7543
-#    Sp: 180
-#    Attack: 526
-#    Attack2: 693
-#    Defense: 18
-#    MagicDefense: 29
-#    Agi: 72
-#    Vit: 45
-#    Int: 35
-#    Dex: 48
-#    Luk: 65
-#    AttackRange: 2
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Demon
-#    Element: Earth
-#    ElementLevel: 4
-#    WalkSpeed: 147
-#    AttackDelay: 516
-#    AttackMotion: 768
-#    DamageMotion: 384
-#    Modes:
-#      NoRandomWalk: true
-#  - Id: 2060
-#    AegisName: M_ALICE
-#    Name: Alice
-#    Level: 62
-#    Hp: 10000
-#    Sp: 221
-#    Attack: 700
-#    Attack2: 850
-#    Defense: 5
-#    MagicDefense: 5
-#    Str: 64
-#    Agi: 64
-#    Vit: 42
-#    Int: 85
-#    Dex: 100
-#    Luk: 130
-#    AttackRange: 1
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Demihuman
-#    Element: Neutral
-#    ElementLevel: 3
-#    WalkSpeed: 200
-#    AttackDelay: 502
-#    AttackMotion: 1999
-#    DamageMotion: 480
-#    Modes:
-#      NoRandomWalk: true
+  - Id: 2058
+    AegisName: M_MIMIC
+    Name: Mimic
+    Level: 51
+    Hp: 6120
+    Sp: 182
+    Attack: 800
+    Attack2: 950
+    Defense: 10
+    MagicDefense: 40
+    Str: 44
+    Agi: 121
+    Int: 60
+    Dex: 75
+    Luk: 110
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Formless
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 100
+    AttackDelay: 972
+    AttackMotion: 500
+    DamageMotion: 288
+    Modes:
+      NoRandomWalk: true
+  - Id: 2059
+    AegisName: M_DISGUISE
+    Name: Disguise
+    Level: 55
+    Hp: 7543
+    Sp: 180
+    Attack: 526
+    Attack2: 693
+    Defense: 18
+    MagicDefense: 29
+    Agi: 72
+    Vit: 45
+    Int: 35
+    Dex: 48
+    Luk: 65
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demon
+    Element: Earth
+    ElementLevel: 4
+    WalkSpeed: 147
+    AttackDelay: 516
+    AttackMotion: 768
+    DamageMotion: 384
+    Modes:
+      NoRandomWalk: true
+  - Id: 2060
+    AegisName: M_ALICE
+    Name: Alice
+    Level: 62
+    Hp: 10000
+    Sp: 221
+    Attack: 700
+    Attack2: 850
+    Defense: 5
+    MagicDefense: 5
+    Str: 64
+    Agi: 64
+    Vit: 42
+    Int: 85
+    Dex: 100
+    Luk: 130
+    AttackRange: 1
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 3
+    WalkSpeed: 200
+    AttackDelay: 502
+    AttackMotion: 1999
+    DamageMotion: 480
+    Modes:
+      NoRandomWalk: true
 #  - Id: 2061
 #    AegisName: E_STAPO
 #    Name: Stapo
@@ -74745,34 +74745,34 @@ Body:
       - Item: Halloween_Coin
         Rate: 10000
         StealProtected: true
-#  - Id: 2937
-#    AegisName: M_LOKI
-#    Name: M Loki
-#    Level: 145
-#    Hp: 1215600
-#    BaseExp: 1
-#    JobExp: 1
-#    Attack: 1835
-#    Attack2: 444
-#    Defense: 15
-#    MagicDefense: 89
-#    Str: 76
-#    Agi: 66
-#    Vit: 90
-#    Int: 55
-#    Dex: 189
-#    Luk: 22
-#    AttackRange: 2
-#    SkillRange: 10
-#    ChaseRange: 12
-#    Size: Medium
-#    Race: Demihuman
-#    Element: Neutral
-#    ElementLevel: 1
-#    WalkSpeed: 175
-#    AttackDelay: 800
-#    AttackMotion: 750
-#    DamageMotion: 300
+  - Id: 2937
+    AegisName: M_LOKI
+    Name: M Loki
+    Level: 145
+    Hp: 1215600
+    BaseExp: 1
+    JobExp: 1
+    Attack: 1835
+    Attack2: 444
+    Defense: 15
+    MagicDefense: 89
+    Str: 76
+    Agi: 66
+    Vit: 90
+    Int: 55
+    Dex: 189
+    Luk: 22
+    AttackRange: 2
+    SkillRange: 10
+    ChaseRange: 12
+    Size: Medium
+    Race: Demihuman
+    Element: Neutral
+    ElementLevel: 1
+    WalkSpeed: 175
+    AttackDelay: 800
+    AttackMotion: 750
+    DamageMotion: 300
   - Id: 2938
     AegisName: MM_MAGIC_SEAL
     Name: Magic Seal

+ 38 - 0
doc/yaml/db/mercenary_db.yml

@@ -0,0 +1,38 @@
+###########################################################################
+# Mercenary Database
+###########################################################################
+#
+# Mercenary Settings
+#
+###########################################################################
+# - Id                      Mercenary ID.
+#   AegisName               Server name to reference the mercenary in scripts and lookups, should use no spaces.
+#   Name                    Name in English.
+#   Level                   Level. (Default: 1)
+#   Hp                      Total HP. (Default: 1)
+#   Sp                      Total SP. (Default: 1)
+#   Attack                  Minimum attack. (Default: 0)
+#   Attack2                 Maximum attack. (Default: 0)
+#   Defense                 Physical defense of the mercenary, reduces melee and ranged physical attack/skill damage. (Default: 0)
+#   MagicDefense            Magic defense of the mercenary, reduces magical skill damage. (Default: 0)
+#   Str                     Strength which affects attack. (Default: 1)
+#   Agi                     Agility which affects flee. (Default: 1)
+#   Vit                     Vitality which affects defense. (Default: 1)
+#   Int                     Intelligence which affects magic attack. (Default: 1)
+#   Dex                     Dexterity which affects hit rate. (Default: 1)
+#   Luk                     Luck which affects perfect dodge/lucky flee/perfect flee/lucky dodge rate. (Default: 1)
+#   AttackRange             Attack range. (Default: 0)
+#   SkillRange              Skill cast range. (Default: 0)
+#   ChaseRange              Chase range. (Default: 0)
+#   Size                    Size. (Default: Small)
+#   Race                    Race. (Default: Formless)
+#   Element                 Element. (Default: Neutral)
+#   ElementLevel            Level of element. (Default: 1)
+#   WalkSpeed               Walk speed. (Default: DEFAULT_WALK_SPEED)
+#   AttackDelay             Attack speed. (Default: 4000)
+#   AttackMotion            Attack animation speed. (Default: 2000)
+#   DamageMotion            Damage animation speed. (Default: 0)
+#   Skills:                 List of mercenary skills. (Optional)
+#     - Name                Skill name.
+#       MaxLevel            Max skill level.
+###########################################################################

+ 1 - 3
src/map/atcommand.cpp

@@ -4036,14 +4036,12 @@ ACMD_FUNC(reload) {
 		mob_reload();
 		pet_db.reload();
 		hom_reload();
-		mercenary_readdb();
-		mercenary_read_skilldb();
+		mercenary_db.reload();
 		elemental_db.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();
-		mercenary_read_skilldb();
 		clif_displaymessage(fd, msg_txt(sd,99)); // Skill database has been reloaded.
 	} else if (strstr(command, "atcommand") || strncmp(message, "atcommand", 4) == 0) {
 		config_t run_test;

+ 1 - 1
src/map/battle.cpp

@@ -124,7 +124,7 @@ int battle_gettarget(struct block_list* bl)
 		case BL_MOB: return ((struct mob_data*)bl)->target_id;
 		case BL_PET: return ((struct pet_data*)bl)->target_id;
 		case BL_HOM: return ((struct homun_data*)bl)->ud.target;
-		case BL_MER: return ((struct mercenary_data*)bl)->ud.target;
+		case BL_MER: return ((s_mercenary_data*)bl)->ud.target;
 		case BL_ELEM: return ((s_elemental_data*)bl)->ud.target;
 	}
 

+ 18 - 19
src/map/clif.cpp

@@ -8309,7 +8309,7 @@ void clif_devotion(struct block_list *src, struct map_session_data *tsd)
 	WBUFL(buf,2) = src->id;
 	if( src->type == BL_MER )
 	{
-		struct mercenary_data *md = BL_CAST(BL_MER,src);
+		s_mercenary_data *md = BL_CAST(BL_MER,src);
 		if( md && md->master && md->devotion_flag )
 			WBUFL(buf,6) = md->master->bl.id;
 
@@ -9836,7 +9836,7 @@ void clif_name( struct block_list* src, struct block_list *bl, send_target targe
 					memcpy(packet.name, ((TBL_HOM *)bl)->homunculus.name, NAME_LENGTH);
 					break;
 				case BL_MER:
-					memcpy(packet.name, ((TBL_MER *)bl)->db->name, NAME_LENGTH);
+					memcpy(packet.name, ((TBL_MER *)bl)->db->name.c_str(), NAME_LENGTH);
 					break;
 				case BL_PET:
 					safestrncpy(packet.name, ((TBL_PET *)bl)->pet.name, NAME_LENGTH);
@@ -12542,7 +12542,7 @@ static void clif_parse_UseSkillToPos_homun(struct homun_data *hd, struct map_ses
 		unit_skilluse_pos(&hd->bl, x, y, skill_id, skill_lv);
 }
 
-static void clif_parse_UseSkillToId_mercenary(struct mercenary_data *md, struct map_session_data *sd, t_tick tick, uint16 skill_id, uint16 skill_lv, int target_id)
+static void clif_parse_UseSkillToId_mercenary(s_mercenary_data *md, struct map_session_data *sd, t_tick tick, uint16 skill_id, uint16 skill_lv, int target_id)
 {
 	int lv;
 
@@ -12566,7 +12566,7 @@ static void clif_parse_UseSkillToId_mercenary(struct mercenary_data *md, struct
 		unit_skilluse_id(&md->bl, target_id, skill_id, skill_lv);
 }
 
-static void clif_parse_UseSkillToPos_mercenary(struct mercenary_data *md, struct map_session_data *sd, t_tick tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo)
+static void clif_parse_UseSkillToPos_mercenary(s_mercenary_data *md, struct map_session_data *sd, t_tick tick, uint16 skill_id, uint16 skill_lv, short x, short y, int skillmoreinfo)
 {
 	int lv;
 	if( !md )
@@ -17696,7 +17696,7 @@ void clif_quest_show_event(struct map_session_data *sd, struct block_list *bl, e
 /// 02a2 <var id>.W <value>.L
 void clif_mercenary_updatestatus(struct map_session_data *sd, int type)
 {
-	struct mercenary_data *md;
+	s_mercenary_data *md;
 	struct status_data *status;
 	int fd;
 	if( !clif_session_isValid(sd) || (md = sd->md) == NULL )
@@ -17765,7 +17765,7 @@ void clif_mercenary_updatestatus(struct map_session_data *sd, int type)
 void clif_mercenary_info(struct map_session_data *sd)
 {
 	int fd;
-	struct mercenary_data *md;
+	s_mercenary_data *md;
 	struct status_data *status;
 	int atk;
 
@@ -17789,7 +17789,7 @@ void clif_mercenary_info(struct map_session_data *sd)
 	WFIFOW(fd,16) = status->mdef;
 	WFIFOW(fd,18) = status->flee;
 	WFIFOW(fd,20) = status->amotion;
-	safestrncpy(WFIFOCP(fd,22), md->db->name, NAME_LENGTH);
+	safestrncpy(WFIFOCP(fd,22), md->db->name.c_str(), NAME_LENGTH);
 	WFIFOW(fd,46) = md->db->lv;
 	WFIFOL(fd,48) = status->hp;
 	WFIFOL(fd,52) = status->max_hp;
@@ -17808,8 +17808,8 @@ void clif_mercenary_info(struct map_session_data *sd)
 /// 029d <packet len>.W { <skill id>.W <type>.L <level>.W <sp cost>.W <attack range>.W <skill name>.24B <upgradable>.B }*
 void clif_mercenary_skillblock(struct map_session_data *sd)
 {
-	struct mercenary_data *md;
-	int fd, i, len = 4;
+	s_mercenary_data *md;
+	int fd, len = 4;
 
 	if( sd == NULL || (md = sd->md) == NULL )
 		return;
@@ -17817,20 +17817,19 @@ void clif_mercenary_skillblock(struct map_session_data *sd)
 	fd = sd->fd;
 	WFIFOHEAD(fd,4+37*MAX_MERCSKILL);
 	WFIFOW(fd,0) = 0x29d;
-	for( i = 0; i < MAX_MERCSKILL; i++ )
-	{
-		uint16 id;
-		short idx = -1;
-		if( (id = md->db->skill[i].id) == 0 )
-			continue;
-		if ((idx = mercenary_skill_get_index(id)) == -1)
+	for (const auto &it : md->db->skill) {
+		uint16 id = it.first;
+
+		if (!SKILL_CHK_MERC(id))
 			continue;
 
+		uint16 lv = it.second;
+
 		WFIFOW(fd,len) = id;
 		WFIFOL(fd,len+2) = skill_get_inf(id);
-		WFIFOW(fd,len+6) = md->db->skill[idx].lv;
-		WFIFOW(fd,len+8) = skill_get_sp(id, md->db->skill[idx].lv);
-		WFIFOW(fd,len+10) = skill_get_range2(&md->bl, id, md->db->skill[idx].lv, false);
+		WFIFOW(fd,len+6) = lv;
+		WFIFOW(fd,len+8) = skill_get_sp(id, lv);
+		WFIFOW(fd,len+10) = skill_get_range2(&md->bl, id, lv, false);
 		safestrncpy(WFIFOCP(fd,len+12), skill_get_name(id), NAME_LENGTH);
 		WFIFOB(fd,len+36) = 0; // Skillable for Mercenary?
 		len += 37;

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

@@ -323,8 +323,7 @@
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\magicmushroom_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\magicmushroom_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\map_cache.dat" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\map_cache.dat')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\map_index.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\map_index.txt')" />
-    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mercenary_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mercenary_db.txt')" />
-    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mercenary_skill_db.txt" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mercenary_skill_db.txt')" />
+    <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mercenary_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mercenary_db.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mob_avail.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mob_avail.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mob_summon.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mob_summon.yml')" />
     <Copy SourceFiles="$(SolutionDir)db\import-tmpl\mob_chat_db.yml" DestinationFolder="$(SolutionDir)db\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)db\import\mob_chat_db.yml')" />

+ 1 - 1
src/map/map.cpp

@@ -2239,7 +2239,7 @@ struct homun_data* map_id2hd(int id){
 	return BL_CAST(BL_HOM, bl);
 }
 
-struct mercenary_data* map_id2mc(int id){
+struct s_mercenary_data* map_id2mc(int id){
 	struct block_list* bl = map_id2bl(id);
 	return BL_CAST(BL_MER, bl);
 }

+ 2 - 2
src/map/map.hpp

@@ -1087,7 +1087,7 @@ struct map_session_data * map_id2sd(int id);
 struct mob_data * map_id2md(int id);
 struct npc_data * map_id2nd(int id);
 struct homun_data* map_id2hd(int id);
-struct mercenary_data* map_id2mc(int id);
+struct s_mercenary_data* map_id2mc(int id);
 struct pet_data* map_id2pd(int id);
 struct s_elemental_data* map_id2ed(int id);
 struct chat_data* map_id2cd(int id);
@@ -1195,7 +1195,7 @@ typedef struct chat_data        TBL_CHAT;
 typedef struct skill_unit       TBL_SKILL;
 typedef struct pet_data         TBL_PET;
 typedef struct homun_data       TBL_HOM;
-typedef struct mercenary_data   TBL_MER;
+typedef struct s_mercenary_data   TBL_MER;
 typedef struct s_elemental_data	TBL_ELEM;
 
 #define BL_CAST(type_, bl) \

+ 485 - 185
src/map/mercenary.cpp

@@ -29,16 +29,7 @@
 
 using namespace rathena;
 
-std::map<uint16, struct s_mercenary_db> mercenary_db_data;
-
-/**
- * Search Mercenary by class
- * @param class_ Class ID of Mercenary
- * @return A pointer to the mercenary db entry or nullptr if not found
- **/
-struct s_mercenary_db *mercenary_db( uint16 class_ ){
-	return util::map_find( mercenary_db_data, class_ );
-}
+MercenaryDatabase mercenary_db;
 
 /**
 * Get View Data of Mercenary by Class ID
@@ -46,7 +37,7 @@ struct s_mercenary_db *mercenary_db( uint16 class_ ){
 * @return View Data of Mercenary
 **/
 struct view_data *mercenary_get_viewdata( uint16 class_ ){
-	struct s_mercenary_db *db = mercenary_db(class_);
+	std::shared_ptr<s_mercenary_db> db = mercenary_db.find(class_);
 
 	if( db ){
 		return &db->vd;
@@ -55,20 +46,6 @@ struct view_data *mercenary_get_viewdata( uint16 class_ ){
 	}
 }
 
-/**
- * Get mercenary skill index for mercenary skill tree
- * @param skill_id
- * @return Index in skill_tree or -1
- **/
-short mercenary_skill_get_index(uint16 skill_id) {
-	if (!SKILL_CHK_MERC(skill_id))
-		return -1;
-	skill_id -= MC_SKILLBASE;
-	if (skill_id >= MAX_MERCSKILL)
-		return -1;
-	return skill_id;
-}
-
 /**
 * Create a new Mercenary for Player
 * @param sd The Player
@@ -76,18 +53,17 @@ short mercenary_skill_get_index(uint16 skill_id) {
 * @param lifetime Contract duration
 * @return false if failed, true otherwise
 **/
-bool mercenary_create(struct map_session_data *sd, uint16 class_, unsigned int lifetime) {
-	struct s_mercenary merc;
-	struct s_mercenary_db *db;
+bool mercenary_create(map_session_data *sd, uint16 class_, unsigned int lifetime) {
 	nullpo_retr(false,sd);
 
-	db = mercenary_db(class_);
+	std::shared_ptr<s_mercenary_db> db = mercenary_db.find(class_);
 
-	if( !db ){
+	if (db == nullptr) {
+		ShowError("mercenary_create: Unknown mercenary class %d.\n", class_);
 		return false;
 	}
 
-	memset(&merc,0,sizeof(struct s_mercenary));
+	s_mercenary merc = {};
 
 	merc.char_id = sd->status.char_id;
 	merc.class_ = class_;
@@ -106,12 +82,11 @@ bool mercenary_create(struct map_session_data *sd, uint16 class_, unsigned int l
 * @param md The Mercenary
 * @return The Lifetime
 **/
-t_tick mercenary_get_lifetime(struct mercenary_data *md) {
-	const struct TimerData * td;
+t_tick mercenary_get_lifetime(s_mercenary_data *md) {
 	if( md == NULL || md->contract_timer == INVALID_TIMER )
 		return 0;
 
-	td = get_timer(md->contract_timer);
+	const struct TimerData *td = get_timer(md->contract_timer);
 	return (td != NULL) ? DIFF_TICK(td->tick, gettick()) : 0;
 }
 
@@ -120,13 +95,11 @@ t_tick mercenary_get_lifetime(struct mercenary_data *md) {
 * @param md Mercenary
 * @return enum e_MercGuildType
 **/
-enum e_MercGuildType mercenary_get_guild(struct mercenary_data *md){
-	uint16 class_;
-
+e_MercGuildType mercenary_get_guild(s_mercenary_data *md){
 	if( md == NULL || md->db == NULL )
 		return NONE_MERC_GUILD;
 
-	class_ = md->db->class_;
+	uint16 class_ = md->db->class_;
 
 	if( class_ >= MERID_MER_ARCHER01 && class_ <= MERID_MER_ARCHER10 )
 		return ARCH_MERC_GUILD;
@@ -143,14 +116,13 @@ enum e_MercGuildType mercenary_get_guild(struct mercenary_data *md){
 * @param md Mercenary
 * @return the Faith value
 **/
-int mercenary_get_faith(struct mercenary_data *md) {
-	struct map_session_data *sd;
-	enum e_MercGuildType guild;
+int mercenary_get_faith(s_mercenary_data *md) {
+	map_session_data *sd;
 
 	if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
 		return 0;
 
-	guild = mercenary_get_guild(md);
+	e_MercGuildType guild = mercenary_get_guild(md);
 
 	switch( guild ){
 		case ARCH_MERC_GUILD:
@@ -170,15 +142,14 @@ int mercenary_get_faith(struct mercenary_data *md) {
 * @param md The Mercenary
 * @param value Faith Value
 **/
-void mercenary_set_faith(struct mercenary_data *md, int value) {
-	struct map_session_data *sd;
-	enum e_MercGuildType guild;
-	int *faith;
+void mercenary_set_faith(s_mercenary_data *md, int value) {
+	map_session_data *sd;
 
 	if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
 		return;
 
-	guild = mercenary_get_guild(md);
+	e_MercGuildType guild = mercenary_get_guild(md);
+	int *faith = nullptr;
 
 	switch( guild ){
 		case ARCH_MERC_GUILD:
@@ -204,14 +175,13 @@ void mercenary_set_faith(struct mercenary_data *md, int value) {
 * @param md Mercenary
 * @return Number of calls
 **/
-int mercenary_get_calls(struct mercenary_data *md) {
-	struct map_session_data *sd;
-	enum e_MercGuildType guild;
+int mercenary_get_calls(s_mercenary_data *md) {
+	map_session_data *sd;
 
 	if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
 		return 0;
 
-	guild = mercenary_get_guild(md);
+	e_MercGuildType guild = mercenary_get_guild(md);
 
 	switch( guild ){
 		case ARCH_MERC_GUILD:
@@ -231,15 +201,14 @@ int mercenary_get_calls(struct mercenary_data *md) {
 * @param md Mercenary
 * @param value
 **/
-void mercenary_set_calls(struct mercenary_data *md, int value) {
-	struct map_session_data *sd;
-	enum e_MercGuildType guild;
-	int *calls;
+void mercenary_set_calls(s_mercenary_data *md, int value) {
+	map_session_data *sd;
 
 	if( md == NULL || md->db == NULL || (sd = md->master) == NULL )
 		return;
 
-	guild = mercenary_get_guild(md);
+	e_MercGuildType guild = mercenary_get_guild(md);
+	int *calls = nullptr;
 
 	switch( guild ){
 		case ARCH_MERC_GUILD:
@@ -263,7 +232,7 @@ void mercenary_set_calls(struct mercenary_data *md, int value) {
 * Save Mercenary data
 * @param md Mercenary
 **/
-void mercenary_save(struct mercenary_data *md) {
+void mercenary_save(s_mercenary_data *md) {
 	md->mercenary.hp = md->battle_status.hp;
 	md->mercenary.sp = md->battle_status.sp;
 	md->mercenary.life_time = mercenary_get_lifetime(md);
@@ -275,8 +244,8 @@ void mercenary_save(struct mercenary_data *md) {
 * Ends contract of Mercenary
 **/
 static TIMER_FUNC(merc_contract_end){
-	struct map_session_data *sd;
-	struct mercenary_data *md;
+	map_session_data *sd;
+	s_mercenary_data *md;
 
 	if( (sd = map_id2sd(id)) == NULL )
 		return 1;
@@ -300,8 +269,8 @@ static TIMER_FUNC(merc_contract_end){
 * @param md Mercenary
 * @param reply
 **/
-int mercenary_delete(struct mercenary_data *md, int reply) {
-	struct map_session_data *sd = md->master;
+int mercenary_delete(s_mercenary_data *md, int reply) {
+	map_session_data *sd = md->master;
 	md->mercenary.life_time = 0;
 
 	mercenary_contract_stop(md);
@@ -329,7 +298,7 @@ int mercenary_delete(struct mercenary_data *md, int reply) {
 * Stop contract of Mercenary
 * @param md Mercenary
 **/
-void mercenary_contract_stop(struct mercenary_data *md) {
+void mercenary_contract_stop(s_mercenary_data *md) {
 	nullpo_retv(md);
 	if( md->contract_timer != INVALID_TIMER )
 		delete_timer(md->contract_timer, merc_contract_end);
@@ -340,7 +309,7 @@ void mercenary_contract_stop(struct mercenary_data *md) {
 * Init contract of Mercenary
 * @param md Mercenary
 **/
-void merc_contract_init(struct mercenary_data *md) {
+void merc_contract_init(s_mercenary_data *md) {
 	if( md->contract_timer == INVALID_TIMER )
 		md->contract_timer = add_timer(gettick() + md->mercenary.life_time, merc_contract_end, md->master->bl.id, 0);
 
@@ -353,30 +322,31 @@ void merc_contract_init(struct mercenary_data *md) {
  * @param flag : if inter-serv request was sucessfull
  * @return false:failure, true:sucess
  */
-bool mercenary_recv_data(struct s_mercenary *merc, bool flag)
+bool mercenary_recv_data(s_mercenary *merc, bool flag)
 {
-	struct map_session_data *sd;
-	struct mercenary_data *md;
-	struct s_mercenary_db *db;
-
-	db = mercenary_db(merc->class_);
+	map_session_data *sd;
 
 	if( (sd = map_charid2sd(merc->char_id)) == NULL )
 		return false;
+
+	std::shared_ptr<s_mercenary_db> db = mercenary_db.find(merc->class_);
+
 	if( !flag || !db ){ // Not created - loaded - DB info
 		sd->status.mer_id = 0;
 		return false;
 	}
 
+	s_mercenary_data *md;
+
 	if( !sd->md ) {
-		sd->md = md = (struct mercenary_data*)aCalloc(1,sizeof(struct mercenary_data));
+		sd->md = md = (s_mercenary_data*)aCalloc(1,sizeof(s_mercenary_data));
 		md->bl.type = BL_MER;
 		md->bl.id = npc_get_new_npc_id();
 		md->devotion_flag = 0;
 
 		md->master = sd;
 		md->db = db;
-		memcpy(&md->mercenary, merc, sizeof(struct s_mercenary));
+		memcpy(&md->mercenary, merc, sizeof(s_mercenary));
 		status_set_viewdata(&md->bl, md->mercenary.class_);
 		status_change_init(&md->bl);
 		unit_dataset(&md->bl);
@@ -395,7 +365,7 @@ bool mercenary_recv_data(struct s_mercenary *merc, bool flag)
 		md->masterteleport_timer = INVALID_TIMER;
 		merc_contract_init(md);
 	} else {
-		memcpy(&sd->md->mercenary, merc, sizeof(struct s_mercenary));
+		memcpy(&sd->md->mercenary, merc, sizeof(s_mercenary));
 		md = sd->md;
 	}
 
@@ -420,7 +390,7 @@ bool mercenary_recv_data(struct s_mercenary *merc, bool flag)
 * @param hp HP amount
 * @param sp SP amount
 **/
-void mercenary_heal(struct mercenary_data *md, int hp, int sp) {
+void mercenary_heal(s_mercenary_data *md, int hp, int sp) {
 	if (md->master == NULL)
 		return;
 	if( hp )
@@ -434,7 +404,7 @@ void mercenary_heal(struct mercenary_data *md, int hp, int sp) {
  * @param md: Mercenary
  * @return false for status_damage
  */
-bool mercenary_dead(struct mercenary_data *md) {
+bool mercenary_dead(s_mercenary_data *md) {
 	mercenary_delete(md, 1);
 	return false;
 }
@@ -443,18 +413,17 @@ bool mercenary_dead(struct mercenary_data *md) {
 * Gives bonus to Mercenary
 * @param md Mercenary
 **/
-void mercenary_killbonus(struct mercenary_data *md) {
-	const enum sc_type scs[] = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP };
-	uint8 index = rnd() % ARRAYLENGTH(scs);
+void mercenary_killbonus(s_mercenary_data *md) {
+	std::vector<sc_type> scs = { SC_MERC_FLEEUP, SC_MERC_ATKUP, SC_MERC_HPUP, SC_MERC_SPUP, SC_MERC_HITUP };
 
-	sc_start(&md->bl,&md->bl, scs[index], 100, rnd() % 5, 600000);
+	sc_start(&md->bl,&md->bl, util::vector_random(scs), 100, rnd() % 5, 600000);
 }
 
 /**
 * Mercenary does kill
 * @param md Mercenary
 **/
-void mercenary_kills(struct mercenary_data *md){
+void mercenary_kills(s_mercenary_data *md){
 	if(md->mercenary.kill_count <= (INT_MAX-1)) //safe cap to INT_MAX
 		md->mercenary.kill_count++;
 
@@ -474,144 +443,475 @@ void mercenary_kills(struct mercenary_data *md){
 * @param skill_id The skill
 * @return Skill Level or 0 if Mercenary doesn't have the skill
 **/
-int mercenary_checkskill(struct mercenary_data *md, uint16 skill_id) {
-	short idx = mercenary_skill_get_index(skill_id);
-
-	if( !md || !md->db || idx == -1)
+uint16 mercenary_checkskill(s_mercenary_data *md, uint16 skill_id) {
+	if (!md || !md->db)
 		return 0;
-	return md->db->skill[idx].lv;
+	auto skill_level = util::umap_find(md->db->skill, skill_id);
+	return skill_level ? *skill_level : 0;
 }
 
-/**
-* Read each line of Mercenary's database
-**/
-static bool mercenary_readdb_sub(char* str[], int columns, int current)
-{
-	int ele;
-	uint16 class_ = atoi(str[0]);
-	struct s_mercenary_db *db;
-	struct status_data *status;
-
-	db = &mercenary_db_data[class_];
-
-	db->class_ = class_;
-	safestrncpy(db->sprite, str[1], NAME_LENGTH);
-	safestrncpy(db->name, str[2], NAME_LENGTH);
-	db->lv = atoi(str[3]);
-
-	status = &db->status;
-	db->vd.class_ = db->class_;
-
-	status->max_hp = atoi(str[4]);
-	status->max_sp = atoi(str[5]);
-	status->rhw.range = atoi(str[6]);
-	status->rhw.atk = atoi(str[7]);
-	status->rhw.atk2 = status->rhw.atk + atoi(str[8]);
-	status->def = atoi(str[9]);
-	status->mdef = atoi(str[10]);
-	status->str = atoi(str[11]);
-	status->agi = atoi(str[12]);
-	status->vit = atoi(str[13]);
-	status->int_ = atoi(str[14]);
-	status->dex = atoi(str[15]);
-	status->luk = atoi(str[16]);
-	db->range2 = atoi(str[17]);
-	db->range3 = atoi(str[18]);
-	status->size = atoi(str[19]);
-	status->race = atoi(str[20]);
-
-	ele = atoi(str[21]);
-	status->def_ele = ele%20;
-	status->ele_lv = (unsigned char)floor(ele/20.);
-	if( !CHK_ELEMENT(status->def_ele) )
-	{
-		ShowWarning("Mercenary %d has invalid element type %d (max element is %d)\n", db->class_, status->def_ele, ELE_ALL - 1);
-		status->def_ele = ELE_NEUTRAL;
+const std::string MercenaryDatabase::getDefaultLocation() {
+	return std::string(db_path) + "/mercenary_db.yml";
+}
+
+/*
+ * Reads and parses an entry from the mercenary_db.
+ * @param node: YAML node containing the entry.
+ * @return count of successfully parsed rows
+ */
+uint64 MercenaryDatabase::parseBodyNode(const YAML::Node &node) {
+	uint32 id;
+
+	if (!this->asUInt32(node, "Id", id))
+		return 0;
+
+	std::shared_ptr<s_mercenary_db> mercenary = this->find(id);
+	bool exists = mercenary != nullptr;
+
+	if (!exists) {
+		if (!this->nodesExist(node, { "AegisName", "Name" }))
+			return 0;
+
+		mercenary = std::make_shared<s_mercenary_db>();
+		mercenary->class_ = id;
 	}
-	if( !CHK_ELEMENT_LEVEL(status->ele_lv) )
-	{
-		ShowWarning("Mercenary %d has invalid element level %d (max is %d)\n", db->class_, status->ele_lv, MAX_ELE_LEVEL);
-		status->ele_lv = 1;
+
+	if (this->nodeExists(node, "AegisName")) {
+		std::string name;
+
+		if (!this->asString(node, "AegisName", name))
+			return 0;
+
+		if (name.size() > NAME_LENGTH) {
+			this->invalidWarning(node["AegisName"], "AegisName \"%s\" exceeds maximum of %d characters, capping...\n", name.c_str(), NAME_LENGTH - 1);
+		}
+
+		name.resize(NAME_LENGTH);
+		mercenary->sprite = name;
+	}
+
+	if (this->nodeExists(node, "Name")) {
+		std::string name;
+
+		if (!this->asString(node, "Name", name))
+			return 0;
+
+		if (name.size() > NAME_LENGTH) {
+			this->invalidWarning(node["Name"], "Name \"%s\" exceeds maximum of %d characters, capping...\n", name.c_str(), NAME_LENGTH - 1);
+		}
+
+		name.resize(NAME_LENGTH);
+		mercenary->name = name;
+	}
+
+	if (this->nodeExists(node, "Level")) {
+		uint16 level;
+
+		if (!this->asUInt16(node, "Level", level))
+			return 0;
+
+		if (level > MAX_LEVEL) {
+			this->invalidWarning(node["Level"], "Level %d exceeds MAX_LEVEL, capping to %d.\n", level, MAX_LEVEL);
+			level = MAX_LEVEL;
+		}
+
+		mercenary->lv = level;
+	} else {
+		if (!exists)
+			mercenary->lv = 1;
 	}
 
-	status->aspd_rate = 1000;
-	status->speed = atoi(str[22]);
-	status->adelay = atoi(str[23]);
-	status->amotion = atoi(str[24]);
-	status->dmotion = atoi(str[25]);
+	if (this->nodeExists(node, "Hp")) {
+		uint32 hp;
+
+		if (!this->asUInt32(node, "Hp", hp))
+			return 0;
+
+		mercenary->status.max_hp = hp;
+	} else {
+		if (!exists)
+			mercenary->status.max_hp = 1;
+	}
 	
-	return true;
-}
+	if (this->nodeExists(node, "Sp")) {
+		uint32 sp;
 
-/**
-* Load Mercenary's database
-**/
-void mercenary_readdb(void) {
-	const char *filename[]={ "mercenary_db.txt",DBIMPORT"/mercenary_db.txt"};
-	uint8 i;
+		if (!this->asUInt32(node, "Sp", sp))
+			return 0;
 
-	mercenary_db_data.clear();
+		mercenary->status.max_sp = sp;
+	} else {
+		if (!exists)
+			mercenary->status.max_sp = 1;
+	}
+
+	if (this->nodeExists(node, "Attack")) {
+		uint16 atk;
+
+		if (!this->asUInt16(node, "Attack", atk))
+			return 0;
 
-	for(i = 0; i<ARRAYLENGTH(filename); i++){
-		sv_readdb(db_path, filename[i], ',', 26, 26, -1, &mercenary_readdb_sub, i > 0);
+		mercenary->status.rhw.atk = atk;
+	} else {
+		if (!exists)
+			mercenary->status.rhw.atk = 0;
 	}
-}
+	
+	if (this->nodeExists(node, "Attack2")) {
+		uint16 atk;
 
-/**
-* Read each line of Mercenary's skill
-**/
-static bool mercenary_read_skilldb_sub(char* str[], int columns, int current)
-{// <merc id>,<skill id>,<skill level>
-	struct s_mercenary_db *db;
-	uint16 class_, skill_id, skill_lv;
-	short idx = -1;
-
-	class_ = atoi(str[0]);
-	db = mercenary_db(class_);
-	if( !db ){
-		ShowError("read_mercenary_skilldb : Class %d not found in mercenary_db for skill entry.\n", class_);
-		return false;
+		if (!this->asUInt16(node, "Attack2", atk))
+			return 0;
+
+		mercenary->status.rhw.atk2 = mercenary->status.rhw.atk + atk;
+	} else {
+		if (!exists)
+			mercenary->status.rhw.atk2 = mercenary->status.rhw.atk;
 	}
 
-	skill_id = atoi(str[1]);
-	if( (idx = mercenary_skill_get_index(skill_id)) == -1 ) {
-		ShowError("read_mercenary_skilldb: Invalid Mercenary skill '%s'.\n", str[1]);
-		return false;
+	if (this->nodeExists(node, "Defense")) {
+		int32 def;
+
+		if (!this->asInt32(node, "Defense", def))
+			return 0;
+
+		if (def < DEFTYPE_MIN || def > DEFTYPE_MAX) {
+			this->invalidWarning(node["Defense"], "Invalid defense %d, capping...\n", def);
+			def = cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX);
+		}
+
+		mercenary->status.def = static_cast<defType>(def);
+	} else {
+		if (!exists)
+			mercenary->status.def = 0;
 	}
+	
+	if (this->nodeExists(node, "MagicDefense")) {
+		int32 def;
 
-	skill_lv = atoi(str[2]);
+		if (!this->asInt32(node, "MagicDefense", def))
+			return 0;
 
-	db->skill[idx].id = skill_id;
-	db->skill[idx].lv = skill_lv;
+		if (def < DEFTYPE_MIN || def > DEFTYPE_MAX) {
+			this->invalidWarning(node["MagicDefense"], "Invalid magic defense %d, capping...\n", def);
+			def = cap_value(def, DEFTYPE_MIN, DEFTYPE_MAX);
+		}
 
-	return true;
-}
+		mercenary->status.mdef = static_cast<defType>(def);
+	} else {
+		if (!exists)
+			mercenary->status.mdef = 0;
+	}
 
-/**
-* Load Mercenary's skill database
-**/
-void mercenary_read_skilldb(void){
-	const char *filename[]={ "mercenary_skill_db.txt",DBIMPORT"/mercenary_skill_db.txt"};
-	uint8 i;
+	if (this->nodeExists(node, "Str")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Str", stat))
+			return 0;
+
+		mercenary->status.str = stat;
+	} else {
+		if (!exists)
+			mercenary->status.str = 1;
+	}
+
+	if (this->nodeExists(node, "Agi")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Agi", stat))
+			return 0;
+
+		mercenary->status.agi = stat;
+	} else {
+		if (!exists)
+			mercenary->status.agi = 1;
+	}
 
-	for(i = 0; i<ARRAYLENGTH(filename); i++){
-		sv_readdb(db_path, filename[i], ',', 3, 3, -1, &mercenary_read_skilldb_sub, i > 0);
+	if (this->nodeExists(node, "Vit")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Vit", stat))
+			return 0;
+
+		mercenary->status.vit = stat;
+	} else {
+		if (!exists)
+			mercenary->status.vit = 1;
 	}
+
+	if (this->nodeExists(node, "Int")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Int", stat))
+			return 0;
+
+		mercenary->status.int_ = stat;
+	} else {
+		if (!exists)
+			mercenary->status.int_ = 1;
+	}
+
+	if (this->nodeExists(node, "Dex")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Dex", stat))
+			return 0;
+
+		mercenary->status.dex = stat;
+	} else {
+		if (!exists)
+			mercenary->status.dex = 1;
+	}
+
+	if (this->nodeExists(node, "Luk")) {
+		uint16 stat;
+
+		if (!this->asUInt16(node, "Luk", stat))
+			return 0;
+
+		mercenary->status.luk = stat;
+	} else {
+		if (!exists)
+			mercenary->status.luk = 1;
+	}
+
+	if (this->nodeExists(node, "AttackRange")) {
+		uint16 range;
+
+		if (!this->asUInt16(node, "AttackRange", range))
+			return 0;
+
+		mercenary->status.rhw.range = range;
+	} else {
+		if (!exists)
+			mercenary->status.rhw.range = 0;
+	}
+	
+	if (this->nodeExists(node, "SkillRange")) {
+		uint16 range;
+
+		if (!this->asUInt16(node, "SkillRange", range))
+			return 0;
+
+		mercenary->range2 = range;
+	} else {
+		if (!exists)
+			mercenary->range2 = 0;
+	}
+	
+	if (this->nodeExists(node, "ChaseRange")) {
+		uint16 range;
+
+		if (!this->asUInt16(node, "ChaseRange", range))
+			return 0;
+
+		mercenary->range3 = range;
+	} else {
+		if (!exists)
+			mercenary->range3 = 0;
+	}
+
+	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"], "Unknown mercenary size %s, defaulting to Small.\n", size.c_str());
+			constant = SZ_SMALL;
+		}
+
+		if (constant < SZ_SMALL || constant > SZ_BIG) {
+			this->invalidWarning(node["Size"], "Invalid mercenary size %s, defaulting to Small.\n", size.c_str());
+			constant = SZ_SMALL;
+		}
+
+		mercenary->status.size = static_cast<e_size>(constant);
+	} else {
+		if (!exists)
+			mercenary->status.size = SZ_SMALL;
+	}
+	
+	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"], "Unknown mercenary race %s, defaulting to Formless.\n", race.c_str());
+			constant = RC_FORMLESS;
+		}
+
+		if (!CHK_RACE(constant)) {
+			this->invalidWarning(node["Race"], "Invalid mercenary race %s, defaulting to Formless.\n", race.c_str());
+			constant = RC_FORMLESS;
+		}
+
+		mercenary->status.race = static_cast<e_race>(constant);
+	} else {
+		if (!exists)
+			mercenary->status.race = RC_FORMLESS;
+	}
+
+	if (this->nodeExists(node, "Element")) {
+		std::string ele;
+
+		if (!this->asString(node, "Element", ele))
+			return 0;
+
+		std::string ele_constant = "ELE_" + ele;
+		int64 constant;
+
+		if (!script_get_constant(ele_constant.c_str(), &constant)) {
+			this->invalidWarning(node["Element"], "Unknown mercenary element %s, defaulting to Neutral.\n", ele.c_str());
+			constant = ELE_NEUTRAL;
+		}
+
+		if (!CHK_ELEMENT(constant)) {
+			this->invalidWarning(node["Element"], "Invalid mercenary element %s, defaulting to Neutral.\n", ele.c_str());
+			constant = ELE_NEUTRAL;
+		}
+
+		mercenary->status.def_ele = static_cast<e_element>(constant);
+	} else {
+		if (!exists)
+			mercenary->status.def_ele = ELE_NEUTRAL;
+	}
+
+	if (this->nodeExists(node, "ElementLevel")) {
+		uint16 level;
+
+		if (!this->asUInt16(node, "ElementLevel", level))
+			return 0;
+
+		if (!CHK_ELEMENT_LEVEL(level)) {
+			this->invalidWarning(node["ElementLevel"], "Invalid mercenary element level %hu, defaulting to 1.\n", level);
+			level = 1;
+		}
+
+		mercenary->status.ele_lv = static_cast<uint8>(level);
+	} else {
+		if (!exists)
+			mercenary->status.ele_lv = 1;
+	}
+
+	if (this->nodeExists(node, "WalkSpeed")) {
+		uint16 speed;
+
+		if (!this->asUInt16(node, "WalkSpeed", speed))
+			return 0;
+
+		if (speed < MIN_WALK_SPEED || speed > MAX_WALK_SPEED) {
+			this->invalidWarning(node["WalkSpeed"], "Invalid mercenary walk speed %hu, capping...\n", speed);
+			speed = cap_value(speed, MIN_WALK_SPEED, MAX_WALK_SPEED);
+		}
+
+		mercenary->status.speed = speed;
+	} else {
+		if (!exists)
+			mercenary->status.speed = DEFAULT_WALK_SPEED;
+	}
+
+	if (this->nodeExists(node, "AttackDelay")) {
+		uint16 speed;
+
+		if (!this->asUInt16(node, "AttackDelay", speed))
+			return 0;
+
+		mercenary->status.adelay = cap_value(speed, 0, 4000);
+	} else {
+		if (!exists)
+			mercenary->status.adelay = 4000;
+	}
+	
+	if (this->nodeExists(node, "AttackMotion")) {
+		uint16 speed;
+
+		if (!this->asUInt16(node, "AttackMotion", speed))
+			return 0;
+
+		mercenary->status.amotion = cap_value(speed, 0, 2000);
+	} else {
+		if (!exists)
+			mercenary->status.amotion = 2000;
+	}
+
+	if (this->nodeExists(node, "DamageMotion")) {
+		uint16 speed;
+
+		if (!this->asUInt16(node, "DamageMotion", speed))
+			return 0;
+
+		mercenary->status.dmotion = speed;
+	} else {
+		if (!exists)
+			mercenary->status.dmotion = 0;
+	}
+
+	mercenary->status.aspd_rate = 1000;
+
+	if (this->nodeExists(node, "Skills")) {
+		const YAML::Node &skillsNode = node["Skills"];
+
+		for (const YAML::Node &skill : skillsNode) {
+			std::string skill_name;
+
+			if (!this->asString(skill, "Name", skill_name))
+				return 0;
+
+			uint16 skill_id = skill_name2id(skill_name.c_str());
+
+			if (skill_id == 0) {
+				this->invalidWarning(skill["Name"], "Invalid skill %s, skipping.\n", skill_name.c_str());
+				return 0;
+			}
+
+			if (!SKILL_CHK_MERC(skill_id)) {
+				this->invalidWarning(skill["Name"], "Skill %s (%u) is out of the mercenary skill range [%u-%u], skipping.\n", skill_name.c_str(), skill_id, MC_SKILLBASE, MC_SKILLBASE + MAX_MERCSKILL - 1);
+				return 0;
+			}
+
+			uint16 level;
+
+			if (!this->asUInt16(skill, "MaxLevel", level))
+				return 0;
+
+			if (level == 0) {
+				if (mercenary->skill.erase(skill_id) == 0)
+					this->invalidWarning(skill["Name"], "Failed to remove %s, the skill doesn't exist for mercenary %hu.\n", skill_name.c_str(), id);
+				continue;
+			}
+
+			mercenary->skill[skill_id] = level;
+		}
+	}
+
+	if (!exists)
+		this->put(id, mercenary);
+
+	return true;
 }
 
 /**
 * Init Mercenary datas
 **/
 void do_init_mercenary(void){
-	mercenary_readdb();
-	mercenary_read_skilldb();
+	mercenary_db.load();
 
-	//add_timer_func_list(mercenary_contract, "mercenary_contract");
+	add_timer_func_list(merc_contract_end, "merc_contract_end");
 }
 
 /**
 * Do Final Mercenary datas
 **/
 void do_final_mercenary(void){
-	mercenary_db_data.clear();
+	mercenary_db.clear();
 }

+ 44 - 42
src/map/mercenary.hpp

@@ -19,7 +19,7 @@ enum e_MercGuildType {
 	SWORD_MERC_GUILD,
 };
 
-enum MERID {
+enum e_MERID {
 	MERID_MER_ARCHER01 = 6017,
 	MERID_MER_ARCHER10 = 6026,
 	MERID_MER_LANCER01,
@@ -29,65 +29,67 @@ enum MERID {
 };
 
 struct s_mercenary_db {
-	int class_;
-	char sprite[NAME_LENGTH], name[NAME_LENGTH];
-	unsigned short lv;
-	short range2, range3;
-	struct status_data status;
-	struct view_data vd;
-	struct {
-		unsigned short id, lv;
-	} skill[MAX_MERCSKILL];
+	int32 class_;
+	std::string sprite, name;
+	uint16 lv;
+	uint16 range2, range3;
+	status_data status;
+	view_data vd;
+	std::unordered_map<uint16, uint16> skill;
 };
 
-struct mercenary_data {
-	struct block_list bl;
-	struct unit_data ud;
-	struct view_data *vd;
-	struct status_data base_status, battle_status;
-	struct status_change sc;
-	struct regen_data regen;
+struct s_mercenary_data {
+	block_list bl;
+	unit_data ud;
+	view_data *vd;
+	status_data base_status, battle_status;
+	status_change sc;
+	regen_data regen;
 
-	struct s_mercenary_db *db;
-	struct s_mercenary mercenary;
+	std::shared_ptr<s_mercenary_db> db;
+	s_mercenary mercenary;
 	std::vector<uint16> blockskill;
 
 	int masterteleport_timer;
-	struct map_session_data *master;
+	map_session_data *master;
 	int contract_timer;
 
 	unsigned devotion_flag : 1;
 };
 
-struct s_mercenary_db *mercenary_db(uint16 class_);
 struct view_data * mercenary_get_viewdata(uint16 class_);
 
-bool mercenary_create(struct map_session_data *sd, uint16 class_, unsigned int lifetime);
-bool mercenary_recv_data(struct s_mercenary *merc, bool flag);
-void mercenary_save(struct mercenary_data *md);
+class MercenaryDatabase : public TypesafeYamlDatabase<int32, s_mercenary_db> {
+public:
+	MercenaryDatabase() : TypesafeYamlDatabase("MERCENARY_DB", 1) {
 
-void mercenary_heal(struct mercenary_data *md, int hp, int sp);
-bool mercenary_dead(struct mercenary_data *md);
+	}
 
-int mercenary_delete(struct mercenary_data *md, int reply);
-void mercenary_contract_stop(struct mercenary_data *md);
+	const std::string getDefaultLocation();
+	uint64 parseBodyNode(const YAML::Node& node);
+};
+
+extern MercenaryDatabase mercenary_db;
+
+bool mercenary_create(map_session_data *sd, uint16 class_, unsigned int lifetime);
+bool mercenary_recv_data(s_mercenary *merc, bool flag);
+void mercenary_save(s_mercenary_data *md);
+
+void mercenary_heal(s_mercenary_data *md, int hp, int sp);
+bool mercenary_dead(s_mercenary_data *md);
 
-t_tick mercenary_get_lifetime(struct mercenary_data *md);
-enum e_MercGuildType mercenary_get_guild(struct mercenary_data *md);
-int mercenary_get_faith(struct mercenary_data *md);
-void mercenary_set_faith(struct mercenary_data *md, int value);
-int mercenary_get_calls(struct mercenary_data *md);
-void mercenary_set_calls(struct mercenary_data *md, int value);
-void mercenary_kills(struct mercenary_data *md);
+int mercenary_delete(s_mercenary_data *md, int reply);
+void mercenary_contract_stop(s_mercenary_data *md);
 
-int mercenary_checkskill(struct mercenary_data *md, uint16 skill_id);
-short mercenary_skill_get_index(uint16 skill_id);
+t_tick mercenary_get_lifetime(s_mercenary_data *md);
+e_MercGuildType mercenary_get_guild(s_mercenary_data *md);
+int mercenary_get_faith(s_mercenary_data *md);
+void mercenary_set_faith(s_mercenary_data *md, int value);
+int mercenary_get_calls(s_mercenary_data *md);
+void mercenary_set_calls(s_mercenary_data *md, int value);
+void mercenary_kills(s_mercenary_data *md);
 
-/**
- * atcommand.cpp required
- **/
-void mercenary_readdb(void);
-void mercenary_read_skilldb(void);
+uint16 mercenary_checkskill(s_mercenary_data *md, uint16 skill_id);
 
 void do_init_mercenary(void);
 void do_final_mercenary(void);

+ 1 - 1
src/map/mob.cpp

@@ -2351,7 +2351,7 @@ void mob_log_damage(struct mob_data *md, struct block_list *src, int damage)
 		}
 		case BL_MER:
 		{
-			struct mercenary_data *mer = (TBL_MER*)src;
+			s_mercenary_data *mer = (TBL_MER*)src;
 			if( mer->master )
 				char_id = mer->master->status.char_id;
 			if( damage )

+ 1 - 1
src/map/pc.hpp

@@ -660,7 +660,7 @@ struct map_session_data {
 
 	struct pet_data *pd;
 	struct homun_data *hd;	// [blackhole89]
-	struct mercenary_data *md;
+	s_mercenary_data *md;
 	s_elemental_data *ed;
 
 	struct s_hate_mob {

+ 4 - 4
src/map/script.cpp

@@ -15363,7 +15363,7 @@ BUILDIN_FUNC(getmercinfo)
 {
 	int type;
 	struct map_session_data* sd;
-	struct mercenary_data* md;
+	s_mercenary_data* md;
 
 	if( !script_charid2sd(3,sd) ){
 		script_pushnil(st);
@@ -15385,7 +15385,7 @@ BUILDIN_FUNC(getmercinfo)
 	{
 		case 0: script_pushint(st,md->mercenary.mercenary_id); break;
 		case 1: script_pushint(st,md->mercenary.class_); break;
-		case 2: script_pushstrcopy(st,md->db->name); break;
+		case 2: script_pushstrcopy(st,md->db->name.c_str()); break;
 		case 3: script_pushint(st,mercenary_get_faith(md)); break;
 		case 4: script_pushint(st,mercenary_get_calls(md)); break;
 		case 5: script_pushint(st,md->mercenary.kill_count); break;
@@ -17995,7 +17995,7 @@ BUILDIN_FUNC(rid2name)
 			case BL_NPC: script_pushstrcopy(st,((TBL_NPC*)bl)->exname); break;
 			case BL_PET: script_pushstrcopy(st,((TBL_PET*)bl)->pet.name); break;
 			case BL_HOM: script_pushstrcopy(st,((TBL_HOM*)bl)->homunculus.name); break;
-			case BL_MER: script_pushstrcopy(st,((TBL_MER*)bl)->db->name); break;
+			case BL_MER: script_pushstrcopy(st,((TBL_MER*)bl)->db->name.c_str()); break;
 			default:
 				ShowError("buildin_rid2name: BL type unknown.\n");
 				script_pushconststr(st,"");
@@ -19877,7 +19877,7 @@ BUILDIN_FUNC(mercenary_create)
 
 	class_ = script_getnum(st,2);
 
-	if( !mercenary_db(class_) )
+	if( !mercenary_db.exists(class_) )
 		return SCRIPT_CMD_SUCCESS;
 
 	contract_time = script_getnum(st,3);

+ 4 - 4
src/map/skill.cpp

@@ -1068,7 +1068,7 @@ bool skill_isNotOk_hom(struct homun_data *hd, uint16 skill_id, uint16 skill_lv)
  * @param md: Mercenary who casted
  * @return true: Skill cannot be used, false: otherwise
  */
-bool skill_isNotOk_mercenary(uint16 skill_id, struct mercenary_data *md)
+bool skill_isNotOk_mercenary(uint16 skill_id, s_mercenary_data *md)
 {
 	nullpo_retr(1, md);
 
@@ -6511,7 +6511,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 	struct map_session_data *sd, *dstsd;
 	struct mob_data *md, *dstmd;
 	struct homun_data *hd;
-	struct mercenary_data *mer;
+	s_mercenary_data *mer;
 	struct status_data *sstatus, *tstatus;
 	struct status_change *tsc;
 	struct status_change_entry *tsce;
@@ -21128,7 +21128,7 @@ int skill_blockhomun_start(struct homun_data *hd, uint16 skill_id, int tick)	//[
 }
 
 TIMER_FUNC(skill_blockmerc_end){
-	struct mercenary_data *md = (TBL_MER*)map_id2bl(id);
+	s_mercenary_data *md = (TBL_MER*)map_id2bl(id);
 
 	if (md) {
 		auto skill = util::vector_get(md->blockskill, (uint16)data);
@@ -21140,7 +21140,7 @@ TIMER_FUNC(skill_blockmerc_end){
 	return 1;
 }
 
-int skill_blockmerc_start(struct mercenary_data *md, uint16 skill_id, int tick)
+int skill_blockmerc_start(s_mercenary_data *md, uint16 skill_id, int tick)
 {
 	nullpo_retr(-1, md);
 

+ 2 - 2
src/map/skill.hpp

@@ -613,7 +613,7 @@ bool skill_check_cloaking(struct block_list *bl, struct status_change_entry *sce
 void skill_enchant_elemental_end(struct block_list *bl, int type);
 bool skill_isNotOk(uint16 skill_id, struct map_session_data *sd);
 bool skill_isNotOk_hom(struct homun_data *hd, uint16 skill_id, uint16 skill_lv);
-bool skill_isNotOk_mercenary(uint16 skill_id, struct mercenary_data *md);
+bool skill_isNotOk_mercenary(uint16 skill_id, s_mercenary_data *md);
 
 bool skill_isNotOk_npcRange(struct block_list *src, uint16 skill_id, uint16 skill_lv, int pos_x, int pos_y);
 
@@ -633,7 +633,7 @@ int skill_blockpc_get(struct map_session_data *sd, int skillid);
 int skill_blockpc_clear(struct map_session_data *sd);
 TIMER_FUNC(skill_blockpc_end);
 int skill_blockhomun_start (struct homun_data*,uint16 skill_id,int);
-int skill_blockmerc_start (struct mercenary_data*,uint16 skill_id,int);
+int skill_blockmerc_start (s_mercenary_data*,uint16 skill_id,int);
 
 
 // (Epoque:) To-do: replace this macro with some sort of skill tree check (rather than hard-coded skill names)

+ 5 - 5
src/map/status.cpp

@@ -5140,10 +5140,10 @@ int status_calc_pc_( struct map_session_data* sd, enum e_status_calc_opt opt ){
  * @param opt: Whether it is first calc or not (0 on level up or status)
  * @return 0
  */
-int status_calc_mercenary_(struct mercenary_data *md, enum e_status_calc_opt opt)
+int status_calc_mercenary_(s_mercenary_data *md, enum e_status_calc_opt opt)
 {
 	struct status_data *status = &md->base_status;
-	struct s_mercenary *merc = &md->mercenary;
+	s_mercenary *merc = &md->mercenary;
 
 	if (opt&SCO_FIRST) {
 		memcpy(status, &md->db->status, sizeof(struct status_data));
@@ -8453,7 +8453,7 @@ const char* status_get_name(struct block_list *bl)
 		case BL_MOB:	return ((TBL_MOB*)bl)->name;
 		case BL_PET:	return ((TBL_PET*)bl)->pet.name;
 		case BL_HOM:	return ((TBL_HOM*)bl)->homunculus.name;
-		//case BL_MER: // They only have database names which are global, not specific to GID.
+		case BL_MER:	return ((TBL_MER *)bl)->db->name.c_str();	// They only have database names which are global, not specific to GID.
 		case BL_NPC:	return ((TBL_NPC*)bl)->name;
 		case BL_ELEM:	return ((TBL_ELEM *)bl)->db->name.c_str(); // They only have database names which are global, not specific to GID.
 	}
@@ -8813,7 +8813,7 @@ void status_set_viewdata(struct block_list *bl, int class_)
 		vd = npc_get_viewdata(class_);
 	else if (homdb_checkid(class_))
 		vd = hom_get_viewdata(class_);
-	else if (mercenary_db(class_))
+	else if (mercenary_db.exists(class_))
 		vd = mercenary_get_viewdata(class_);
 	else if (elemental_db.exists(class_))
 		vd = elemental_get_viewdata(class_);
@@ -8943,7 +8943,7 @@ void status_set_viewdata(struct block_list *bl, int class_)
 		break;
 	case BL_MER:
 		{
-			struct mercenary_data *md = (struct mercenary_data*)bl;
+			s_mercenary_data *md = (s_mercenary_data*)bl;
 			if (vd)
 				md->vd = vd;
 			else

+ 2 - 2
src/map/status.hpp

@@ -17,7 +17,7 @@ struct block_list;
 struct mob_data;
 struct pet_data;
 struct homun_data;
-struct mercenary_data;
+struct s_mercenary_data;
 struct s_elemental_data;
 struct npc_data;
 struct status_change;
@@ -2881,7 +2881,7 @@ int status_calc_mob_(struct mob_data* md, enum e_status_calc_opt opt);
 void status_calc_pet_(struct pet_data* pd, enum e_status_calc_opt opt);
 int status_calc_pc_(struct map_session_data* sd, enum e_status_calc_opt opt);
 int status_calc_homunculus_(struct homun_data *hd, enum e_status_calc_opt opt);
-int status_calc_mercenary_(struct mercenary_data *md, enum e_status_calc_opt opt);
+int status_calc_mercenary_(s_mercenary_data *md, e_status_calc_opt opt);
 int status_calc_elemental_(s_elemental_data *ed, e_status_calc_opt opt);
 int status_calc_npc_(struct npc_data *nd, enum e_status_calc_opt opt);
 

+ 3 - 3
src/map/unit.cpp

@@ -65,7 +65,7 @@ struct unit_data* unit_bl2ud(struct block_list *bl)
 	case BL_PET: return &((struct pet_data*)bl)->ud;
 	case BL_NPC: return &((struct npc_data*)bl)->ud;
 	case BL_HOM: return &((struct homun_data*)bl)->ud;
-	case BL_MER: return &((struct mercenary_data*)bl)->ud;
+	case BL_MER: return &((s_mercenary_data*)bl)->ud;
 	case BL_ELEM: return &((s_elemental_data*)bl)->ud;
 	default : return NULL;
 	}
@@ -3216,7 +3216,7 @@ int unit_remove_map_(struct block_list *bl, clr_type clrtype, const char* file,
 			break;
 		}
 		case BL_MER: {
-			struct mercenary_data *md = (struct mercenary_data *)bl;
+			s_mercenary_data *md = (s_mercenary_data *)bl;
 
 			ud->canact_tick = ud->canmove_tick;
 
@@ -3557,7 +3557,7 @@ int unit_free(struct block_list *bl, clr_type clrtype)
 			break;
 		}
 		case BL_MER: {
-			struct mercenary_data *md = (TBL_MER*)bl;
+			s_mercenary_data *md = (TBL_MER*)bl;
 			struct map_session_data *sd = md->master;
 
 			if( mercenary_get_lifetime(md) > 0 )

+ 122 - 0
src/tool/csv2yaml.cpp

@@ -130,6 +130,14 @@ static void elemental_skill_txt_data(const std::string& modePath, const std::str
 		sv_readdb(fixedPath.c_str(), "elemental_skill_db.txt", ',', 4, 4, -1, read_elemental_skilldb, false);
 }
 
+// Mercenary's Skill Database data to memory
+static void mercenary_skill_txt_data(const std::string& modePath, const std::string& fixedPath) {
+	mercenary_skill_tree.clear();
+
+	if (fileExists(fixedPath + "/mercenary_skill_db.txt"))
+		sv_readdb(fixedPath.c_str(), "mercenary_skill_db.txt", ',', 3, 3, -1, mercenary_read_skilldb, false);
+}
+
 template<typename Func>
 bool process( const std::string& type, uint32 version, const std::vector<std::string>& paths, const std::string& name, Func lambda, const std::string& rename = "" ){
 	for( const std::string& path : paths ){
@@ -489,6 +497,12 @@ int do_init( int argc, char** argv ){
 		return 0;
 	}
 
+	mercenary_skill_txt_data(path_db_mode, path_db);
+	if (!process("MERCENARY_DB", 1, root_paths, "mercenary_db", [](const std::string &path, const std::string &name_ext) -> bool {
+		return sv_readdb(path.c_str(), name_ext.c_str(), ',', 26, 26, -1, &mercenary_readdb, false);
+	})) {
+		return 0;
+	}
 	// TODO: add implementations ;-)
 
 	return 0;
@@ -4528,3 +4542,111 @@ static bool read_elementaldb(char* str[], int columns, int current) {
 	return true;
 }
 
+
+// mercenary_db.yml function
+//---------------------------
+static bool mercenary_read_skilldb(char* str[], int columns, int current) {
+	uint16 skill_id = atoi(str[1]);
+	std::string *skill_name = util::umap_find(aegis_skillnames, skill_id);
+
+	if (skill_name == nullptr) {
+		ShowError("mercenary_read_skilldb: Invalid skill '%hu'.\n", skill_id);
+		return false;
+	}
+
+	s_mercenary_skill_csv entry = {};
+
+	entry.skill_name = *skill_name;
+	entry.max_lv = atoi(str[2]);
+
+	uint16 class_ = atoi(str[0]);
+
+	if (util::umap_find(mercenary_skill_tree, class_))
+		mercenary_skill_tree[class_].push_back(entry);
+	else {
+		mercenary_skill_tree[class_] = std::vector<s_mercenary_skill_csv>();
+		mercenary_skill_tree[class_].push_back(entry);
+	}
+
+	return true;
+}
+
+// Copied and adjusted from mercenary.cpp
+static bool mercenary_readdb(char* str[], int columns, int current) {
+	body << YAML::BeginMap;
+	body << YAML::Key << "Id" << YAML::Value << str[0];
+	body << YAML::Key << "AegisName" << YAML::Value << str[1];
+	body << YAML::Key << "Name" << YAML::Value << str[2];
+	if (atoi(str[3]) != 1)
+		body << YAML::Key << "Level" << YAML::Value << str[3];
+	if (atoi(str[4]) != 1)
+		body << YAML::Key << "Hp" << YAML::Value << str[4];
+	if (atoi(str[5]) != 1)
+		body << YAML::Key << "Sp" << YAML::Value << str[5];
+	if (atoi(str[7]) != 0)
+		body << YAML::Key << "Attack" << YAML::Value << str[7];
+	if (atoi(str[8]) != 0)
+		body << YAML::Key << "Attack2" << YAML::Value << str[8];
+	if (atoi(str[9]) != 0)
+		body << YAML::Key << "Defense" << YAML::Value << str[9];
+	if (atoi(str[10]) != 0)
+		body << YAML::Key << "MagicDefense" << YAML::Value << str[10];
+	if (atoi(str[11]) != 1)
+		body << YAML::Key << "Str" << YAML::Value << str[11];
+	if (atoi(str[12]) != 1)
+		body << YAML::Key << "Agi" << YAML::Value << str[12];
+	if (atoi(str[13]) != 1)
+		body << YAML::Key << "Vit" << YAML::Value << str[13];
+	if (atoi(str[14]) != 1)
+		body << YAML::Key << "Int" << YAML::Value << str[14];
+	if (atoi(str[15]) != 1)
+		body << YAML::Key << "Dex" << YAML::Value << str[15];
+	if (atoi(str[16]) != 1)
+		body << YAML::Key << "Luk" << YAML::Value << str[16];
+	if (atoi(str[6]) != 0)
+		body << YAML::Key << "AttackRange" << YAML::Value << str[6];
+	if (atoi(str[17]) != 0)
+		body << YAML::Key << "SkillRange" << YAML::Value << str[17];
+	if (atoi(str[18]) != 0)
+		body << YAML::Key << "ChaseRange" << YAML::Value << str[18];
+	if (atoi(str[19]) != 0)
+		body << YAML::Key << "Size" << YAML::Value << constant_lookup(strtol(str[19], nullptr, 10), "Size_") + 5;
+	if (atoi(str[20]) != 0)
+		body << YAML::Key << "Race" << YAML::Value << name2Upper(constant_lookup(atoi(str[20]), "RC_") + 3);
+
+	int ele = strtol(str[21], nullptr, 10);
+	if (atoi(str[21]) != 0)
+		body << YAML::Key << "Element" << YAML::Value << name2Upper(constant_lookup(ele % 20, "ELE_") + 4);
+	if (atoi(str[21]) != 1)
+		body << YAML::Key << "ElementLevel" << YAML::Value << floor(ele / 20.);
+
+	if (atoi(str[22]) != 0)
+		body << YAML::Key << "WalkSpeed" << YAML::Value << cap_value(std::stoi(str[22]), MIN_WALK_SPEED, MAX_WALK_SPEED);
+	if (atoi(str[23]) != 0)
+		body << YAML::Key << "AttackDelay" << YAML::Value << str[23];
+	if (atoi(str[24]) != 0)
+		body << YAML::Key << "AttackMotion" << YAML::Value << str[24];
+	if (atoi(str[25]) != 0)
+		body << YAML::Key << "DamageMotion" << YAML::Value << str[25];
+
+	for (const auto &skillit : mercenary_skill_tree) {
+		if (skillit.first != atoi(str[0]))
+			continue;
+
+		body << YAML::Key << "Skills";
+		body << YAML::BeginSeq;
+
+		for (const auto &it : skillit.second) {
+			body << YAML::BeginMap;
+			body << YAML::Key << "Name" << YAML::Value << it.skill_name;
+			body << YAML::Key << "MaxLevel" << YAML::Value << it.max_lv;
+			body << YAML::EndMap;
+		}
+
+		body << YAML::EndSeq;
+	}
+
+	body << YAML::EndMap;
+
+	return true;
+}

+ 9 - 0
src/tool/csv2yaml.hpp

@@ -128,6 +128,13 @@ struct s_elemental_skill_csv {
 
 std::unordered_map<uint16, std::vector<s_elemental_skill_csv>> elemental_skill_tree;
 
+struct s_mercenary_skill_csv {
+	std::string skill_name;
+	uint16 max_lv;
+};
+
+std::unordered_map<uint16, std::vector<s_mercenary_skill_csv>> mercenary_skill_tree;
+
 static std::map<std::string, int> um_mapid2jobname {
 	{ "Novice", JOB_NOVICE }, // Novice and Super Novice share the same value
 	{ "SuperNovice", JOB_NOVICE },
@@ -486,5 +493,7 @@ static bool pc_readdb_job_basehpsp(char *fields[], int columns, int current);
 static bool pc_readdb_job1(char *fields[], int columns, int current);
 static bool read_elemental_skilldb(char* str[], int columns, int current);
 static bool read_elementaldb(char* str[], int columns, int current);
+static bool mercenary_read_skilldb(char* str[], int columns, int current);
+static bool mercenary_readdb(char* str[], int columns, int current);
 
 #endif /* CSV2YAML_HPP */