diff --git a/Maple2.File.Parser/Maple2.File.Parser.csproj b/Maple2.File.Parser/Maple2.File.Parser.csproj
index c1af205..577b591 100644
--- a/Maple2.File.Parser/Maple2.File.Parser.csproj
+++ b/Maple2.File.Parser/Maple2.File.Parser.csproj
@@ -13,7 +13,7 @@
MapleStory2, File, Parser, m2d, xml
true
- 2.4.23
+ 2.4.24
net8.0
README.md
enable
diff --git a/Maple2.File.Parser/TableParser.cs b/Maple2.File.Parser/TableParser.cs
index 4c126e9..c45e1f4 100644
--- a/Maple2.File.Parser/TableParser.cs
+++ b/Maple2.File.Parser/TableParser.cs
@@ -123,6 +123,8 @@ public class TableParser {
private readonly XmlSerializer maidRecipeSerializer;
private readonly XmlSerializer maidRecipeGroupSerializer;
private readonly XmlSerializer maidSalarySerializer;
+ private readonly XmlSerializer reactorSerializer;
+ private readonly XmlSerializer reactorStateSerializer;
private readonly string locale;
private readonly string language;
@@ -240,6 +242,8 @@ public TableParser(M2dReader xmlReader, string language) {
maidRecipeSerializer = new XmlSerializer(typeof(MaidRecipeRoot));
maidRecipeGroupSerializer = new XmlSerializer(typeof(MaidRecipeGroupRoot));
maidSalarySerializer = new XmlSerializer(typeof(MaidSalaryRoot));
+ reactorSerializer = new XmlSerializer(typeof(ReactorRoot));
+ reactorStateSerializer = new XmlSerializer(typeof(ReactorStateRoot));
locale = FeatureLocaleFilter.Locale.ToLower();
this.language = language;
@@ -1824,4 +1828,24 @@ public IEnumerable ParseJobTableNew() {
yield return (entry.id, entry);
}
}
+
+ public IEnumerable<(int Id, Reactor Reactor)> ParseReactor() {
+ XmlReader reader = xmlReader.GetXmlReader(xmlReader.GetEntry("table/reactor.xml"));
+ var data = reactorSerializer.Deserialize(reader) as ReactorRoot;
+ Debug.Assert(data != null);
+
+ foreach (Reactor reactor in data.reactor) {
+ yield return (reactor.id, reactor);
+ }
+ }
+
+ public IEnumerable<(int Id, ReactorState State)> ParseReactorState() {
+ XmlReader reader = xmlReader.GetXmlReader(xmlReader.GetEntry("table/reactorstate.xml"));
+ var data = reactorStateSerializer.Deserialize(reader) as ReactorStateRoot;
+ Debug.Assert(data != null);
+
+ foreach (ReactorState state in data.reactorState) {
+ yield return (state.id, state);
+ }
+ }
}
diff --git a/Maple2.File.Parser/Xml/Table/Reactor.cs b/Maple2.File.Parser/Xml/Table/Reactor.cs
new file mode 100644
index 0000000..933fd17
--- /dev/null
+++ b/Maple2.File.Parser/Xml/Table/Reactor.cs
@@ -0,0 +1,29 @@
+using System.Xml.Serialization;
+using M2dXmlGenerator;
+
+namespace Maple2.File.Parser.Xml.Table;
+
+// ./data/xml/table/reactor.xml
+[XmlRoot("ms2")]
+public partial class ReactorRoot {
+ [M2dFeatureLocale(Selector = "id")] private IList _reactor;
+}
+
+public partial class Reactor : IFeatureLocale {
+ [XmlAttribute] public int id;
+ [XmlAttribute] public float reactDistance;
+ // Bracket-wrapped comma list, e.g. "[100, 101, 102, 103]". Consumers strip
+ // the surrounding [] and split on ','; M2dArray cannot parse it directly
+ // because int.Parse would choke on the leading '[' / trailing ']' tokens.
+ [XmlAttribute] public string reactorStateList = string.Empty;
+ [XmlAttribute] public string connectedEffect = string.Empty;
+ // gameEventId links this reactor definition to a GameEvent whose eventType is
+ // "reactor" (GMS2 client RE: reactor+0x70 read by CReactor_CheckReactorGameEvent).
+ [XmlAttribute] public int gameEventId;
+ // Escaped XML fragment: .
+ // Consumers deserialize this separately if they need the reward item list.
+ [XmlAttribute] public string rewardItemListXml = string.Empty;
+ [XmlAttribute] public int reactTimeMSec = 15000;
+ [XmlAttribute] public string reactAnimation = string.Empty;
+ [XmlAttribute] public string createAnimation = string.Empty;
+}
diff --git a/Maple2.File.Parser/Xml/Table/ReactorState.cs b/Maple2.File.Parser/Xml/Table/ReactorState.cs
new file mode 100644
index 0000000..e79b2b5
--- /dev/null
+++ b/Maple2.File.Parser/Xml/Table/ReactorState.cs
@@ -0,0 +1,29 @@
+using System.Xml.Serialization;
+
+namespace Maple2.File.Parser.Xml.Table;
+
+// ./data/xml/table/reactorstate.xml
+[XmlRoot("ms2")]
+public class ReactorStateRoot {
+ [XmlElement] public List reactorState;
+}
+
+public class ReactorState {
+ [XmlAttribute] public int id;
+ // Observed values: connect, sleep, reward.
+ [XmlAttribute] public string type = string.Empty;
+ [XmlAttribute] public int nextTimeSec;
+ [XmlAttribute] public int expireTimeSec;
+ [XmlAttribute] public string kfmName = string.Empty;
+ [XmlAttribute] public string startAni = string.Empty;
+ [XmlAttribute] public string idleAni = string.Empty;
+ [XmlAttribute] public string actionStringKey = string.Empty;
+
+ // Only present on states of type="connect"; absent (self-closing tag) on other states.
+ [XmlElement] public CondItem condItem;
+
+ public class CondItem {
+ [XmlAttribute] public int code;
+ [XmlAttribute] public int consume;
+ }
+}
diff --git a/Maple2.File.Tests/TableParserTest.cs b/Maple2.File.Tests/TableParserTest.cs
index 3ba5cbb..e67ade7 100644
--- a/Maple2.File.Tests/TableParserTest.cs
+++ b/Maple2.File.Tests/TableParserTest.cs
@@ -888,4 +888,18 @@ public void TestMaid() {
continue;
}
}
+
+ [TestMethod]
+ public void TestReactor() {
+ foreach ((_, _) in _parser.ParseReactor()) {
+ continue;
+ }
+ }
+
+ [TestMethod]
+ public void TestReactorState() {
+ foreach ((_, _) in _parser.ParseReactorState()) {
+ continue;
+ }
+ }
}