diff --git a/NetStone/Model/Parseables/Character/ClassJob/CharacterClassJob.cs b/NetStone/Model/Parseables/Character/ClassJob/CharacterClassJob.cs
index 2850532..8301698 100644
--- a/NetStone/Model/Parseables/Character/ClassJob/CharacterClassJob.cs
+++ b/NetStone/Model/Parseables/Character/ClassJob/CharacterClassJob.cs
@@ -21,6 +21,16 @@ public CharacterClassJob(HtmlNode rootNode, CharacterClassJobDefinition definiti
this.definition = definition;
}
+ ///
+ /// Information about the Eureka class.
+ ///
+ public ClassJobEureka? Eureka => new ClassJobEureka(this.RootNode, this.definition.Eureka).GetOptional();
+
+ ///
+ /// Information about the Bozja class.
+ ///
+ public ClassJobBozja? Bozja => new ClassJobBozja(this.RootNode, this.definition.Bozja).GetOptional();
+
///
/// Information about the Paladin class.
///
@@ -65,7 +75,7 @@ public CharacterClassJob(HtmlNode rootNode, CharacterClassJobDefinition definiti
/// Information about the Reaper class.
///
public ClassJobEntry Reaper => new(this.RootNode, this.definition.Reaper);
-
+
///
/// Information about the Viper class.
///
@@ -214,7 +224,7 @@ public CharacterClassJob(HtmlNode rootNode, CharacterClassJobDefinition definiti
{ StaticData.ClassJob.Samurai, this.Samurai },
{ StaticData.ClassJob.Reaper, this.Reaper },
-
+
{ StaticData.ClassJob.Viper, this.Viper },
{ StaticData.ClassJob.Conjurer, this.WhiteMage },
@@ -239,7 +249,7 @@ public CharacterClassJob(HtmlNode rootNode, CharacterClassJobDefinition definiti
{ StaticData.ClassJob.BlackMage, this.BlackMage },
{ StaticData.ClassJob.RedMage, this.RedMage },
-
+
{ StaticData.ClassJob.Pictomancer, this.Pictomancer },
{ StaticData.ClassJob.BlueMage, this.BlueMage },
diff --git a/NetStone/Model/Parseables/Character/ClassJob/ClassJobBozja.cs b/NetStone/Model/Parseables/Character/ClassJob/ClassJobBozja.cs
new file mode 100644
index 0000000..a7d613b
--- /dev/null
+++ b/NetStone/Model/Parseables/Character/ClassJob/ClassJobBozja.cs
@@ -0,0 +1,125 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using HtmlAgilityPack;
+using NetStone.Definitions.Model.Character;
+
+namespace NetStone.Model.Parseables.Character.ClassJob;
+
+///
+/// Entry for Bozja Field Operation of a character
+///
+public class ClassJobBozja : LodestoneParseable, IOptionalParseable
+{
+ private readonly ClassJobBozjaDefinition definition;
+
+ ///
+ /// Constructs a new class entry
+ ///
+ /// Root node of this entry
+ /// Parser definition
+ public ClassJobBozja(HtmlNode rootNode, ClassJobBozjaDefinition definition) : base(rootNode)
+ {
+ this.definition = definition;
+ }
+
+ ///
+ /// The name of this class and job combo.
+ ///
+ public string Name => ParseTooltip(this.definition.NAME);
+
+ ///
+ /// Value indicating whether this class has its job unlocked.
+ ///
+ public bool IsJobUnlocked => this.Name.Contains("/");
+
+ ///
+ /// The level this class or job is at.
+ ///
+ public int Level
+ {
+ get
+ {
+ var level = Parse(this.definition.LEVEL);
+ return level == "-" ? 0 : int.Parse(level);
+ }
+ }
+
+ private string ExpString => ParseInnerText(this.definition.METTLE);
+
+ private long? expCurrentVal;
+
+ ///
+ /// The amount of current achieved EXP on this level.
+ ///
+ public long ExpCurrent
+ {
+ get
+ {
+ if (!this.expCurrentVal.HasValue)
+ ParseExp();
+
+ return this.expCurrentVal!.Value;
+ }
+ }
+
+ private long? expMaxVal;
+
+ ///
+ /// The amount of EXP to be reached to gain the next level.
+ ///
+ public long ExpMax
+ {
+ get
+ {
+ if (!this.expCurrentVal.HasValue)
+ ParseExp();
+
+ return this.expMaxVal!.Value;
+ }
+ }
+
+ ///
+ /// The outstanding amount of EXP to go to the next level.
+ ///
+ public long ExpToGo => this.ExpMax - this.ExpCurrent;
+
+ private void ParseExp()
+ {
+ if (!this.Exists)
+ {
+ this.expCurrentVal = 0;
+ this.expMaxVal = 0;
+
+ return;
+ }
+
+ var expVals = this.ExpString.Split(" / ").Select(x => x.Replace(",", string.Empty)).ToArray();
+
+ if (expVals[0] == "--")
+ {
+ this.expCurrentVal = 0;
+ this.expMaxVal = 0;
+
+ return;
+ }
+
+ this.expCurrentVal = long.Parse(Regex.Match(expVals[0], @"\d+").Value);
+ this.expMaxVal = long.Parse(Regex.Match(expVals[1], @"\d+").Value);
+ }
+
+ ///
+ /// Value indicating if this class is unlocked.
+ ///
+ public bool Exists => this.Level != 0;
+
+ ///
+ /// Value indicating if this class is unlocked.
+ ///
+ public bool IsUnlocked => this.Exists;
+
+ ///
+ /// The string representation of this object.
+ ///
+ /// Name (Level)
+ public override string ToString() => $"{this.Name} ({this.Level})";
+}
\ No newline at end of file
diff --git a/NetStone/Model/Parseables/Character/ClassJob/ClassJobEureka.cs b/NetStone/Model/Parseables/Character/ClassJob/ClassJobEureka.cs
new file mode 100644
index 0000000..233d866
--- /dev/null
+++ b/NetStone/Model/Parseables/Character/ClassJob/ClassJobEureka.cs
@@ -0,0 +1,124 @@
+using System.Linq;
+using HtmlAgilityPack;
+using NetStone.Definitions.Model.Character;
+
+namespace NetStone.Model.Parseables.Character.ClassJob;
+
+///
+/// Entry for Eureka Field Operation of a character
+///
+public class ClassJobEureka : LodestoneParseable, IOptionalParseable
+{
+ private readonly ClassJobEurekaDefinition definition;
+
+ ///
+ /// Constructs a new class entry
+ ///
+ /// Root node of this entry
+ /// Parser definition
+ public ClassJobEureka(HtmlNode rootNode, ClassJobEurekaDefinition definition) : base(rootNode)
+ {
+ this.definition = definition;
+ }
+
+ ///
+ /// The name of this class and job combo.
+ ///
+ public string Name => ParseTooltip(this.definition.Name);
+
+ ///
+ /// Value indicating whether this class has its job unlocked.
+ ///
+ public bool IsJobUnlocked => this.Name.Contains("/");
+
+ ///
+ /// The level this class or job is at.
+ ///
+ public int Level
+ {
+ get
+ {
+ var level = Parse(this.definition.Level);
+ return level == "-" ? 0 : int.Parse(level);
+ }
+ }
+
+ private string ExpString => ParseInnerText(this.definition.Exp);
+
+ private long? expCurrentVal;
+
+ ///
+ /// The amount of current achieved EXP on this level.
+ ///
+ public long ExpCurrent
+ {
+ get
+ {
+ if (!this.expCurrentVal.HasValue)
+ ParseExp();
+
+ return this.expCurrentVal!.Value;
+ }
+ }
+
+ private long? expMaxVal;
+
+ ///
+ /// The amount of EXP to be reached to gain the next level.
+ ///
+ public long ExpMax
+ {
+ get
+ {
+ if (!this.expCurrentVal.HasValue)
+ ParseExp();
+
+ return this.expMaxVal!.Value;
+ }
+ }
+
+ ///
+ /// The outstanding amount of EXP to go to the next level.
+ ///
+ public long ExpToGo => this.ExpMax - this.ExpCurrent;
+
+ private void ParseExp()
+ {
+ if (!this.Exists)
+ {
+ this.expCurrentVal = 0;
+ this.expMaxVal = 0;
+
+ return;
+ }
+
+ var expVals = this.ExpString.Split(" / ").Select(x => x.Replace(",", string.Empty)).ToArray();
+
+ if (expVals[0] == "--")
+ {
+ this.expCurrentVal = 0;
+ this.expMaxVal = 0;
+
+ return;
+ }
+
+ this.expCurrentVal = long.Parse(expVals[0]);
+ this.expMaxVal = long.Parse(expVals[1]);
+ }
+
+ ///
+ /// Value indicating if this class is unlocked.
+ ///
+ public bool Exists => this.Level != 0;
+
+ ///
+ /// Value indicating if this class is unlocked.
+ ///
+ public bool IsUnlocked => this.Exists;
+
+ ///
+ /// The string representation of this object.
+ ///
+ /// Name (Level)
+ public override string ToString() => $"{this.Name} ({this.Level})";
+}
\ No newline at end of file