From 74ab17e2e873f83ca28c9f054fe2980978cd7b1b Mon Sep 17 00:00:00 2001 From: Zin <62830952+Zintixx@users.noreply.github.com> Date: Sun, 18 Oct 2026 20:16:02 -0700 Subject: [PATCH] Parse table/Server/DungeonScaleStat.xml The server table carries the same scaleStatRate, scaleBaseTap, scaleBaseDef and scaleBaseSpaRate attributes as the per-NPC scale* stats, one row per dungeonScaleStatID. No dungeonroom column references it in the NA or KMS2 data, so a server that wants per-dungeon stat scaling keys its own rows on it. Bumps the package to 2.4.29. Co-Authored-By: Claude Opus 5.5 --- Maple2.File.Parser/Maple2.File.Parser.csproj | 2 +- Maple2.File.Parser/ServerTableParser.cs | 12 ++++++++++++ .../Xml/Table/Server/DungeonScaleStat.cs | 18 ++++++++++++++++++ Maple2.File.Tests/ServerTableParserTest.cs | 16 ++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Maple2.File.Parser/Xml/Table/Server/DungeonScaleStat.cs diff --git a/Maple2.File.Parser/Maple2.File.Parser.csproj b/Maple2.File.Parser/Maple2.File.Parser.csproj index 985a1a1..2a37cf9 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.28 + 2.4.29 net8.0 README.md enable diff --git a/Maple2.File.Parser/ServerTableParser.cs b/Maple2.File.Parser/ServerTableParser.cs index 0e84ba4..3b5cecc 100644 --- a/Maple2.File.Parser/ServerTableParser.cs +++ b/Maple2.File.Parser/ServerTableParser.cs @@ -66,6 +66,7 @@ public class ServerTableParser { private readonly XmlSerializer maidGradeInfoSerializer; private readonly XmlSerializer maidRecipeSerializer; private readonly XmlSerializer defaultCharacterInfoSerializer; + private readonly XmlSerializer dungeonScaleStatSerializer; public ServerTableParser(M2dReader xmlReader) { this.xmlReader = xmlReader; @@ -113,6 +114,7 @@ public ServerTableParser(M2dReader xmlReader) { maidGradeInfoSerializer = new XmlSerializer(typeof(MaidGradeInfoRoot)); maidRecipeSerializer = new XmlSerializer(typeof(MaidRecipeRoot)); defaultCharacterInfoSerializer = new XmlSerializer(typeof(DefaultCharacterInfoRoot)); + dungeonScaleStatSerializer = new XmlSerializer(typeof(DungeonScaleStatRoot)); // var seen = new HashSet(); // this.bankTypeSerializer.UnknownAttribute += (sender, args) => { @@ -768,4 +770,14 @@ public IEnumerable ParseNpcStatFactorByLevel() { yield return (info.value, info); } } + + public IEnumerable<(int Id, DungeonScaleStat ScaleStat)> ParseDungeonScaleStat() { + XmlReader reader = xmlReader.GetXmlReader(xmlReader.GetEntry("table/Server/DungeonScaleStat.xml")); + var data = dungeonScaleStatSerializer.Deserialize(reader) as DungeonScaleStatRoot; + Debug.Assert(data != null); + + foreach (DungeonScaleStat entry in data.DungeonScaleStat) { + yield return (entry.dungeonScaleStatID, entry); + } + } } diff --git a/Maple2.File.Parser/Xml/Table/Server/DungeonScaleStat.cs b/Maple2.File.Parser/Xml/Table/Server/DungeonScaleStat.cs new file mode 100644 index 0000000..54509b3 --- /dev/null +++ b/Maple2.File.Parser/Xml/Table/Server/DungeonScaleStat.cs @@ -0,0 +1,18 @@ +using System.Xml.Serialization; + +namespace Maple2.File.Parser.Xml.Table.Server; + +// ./data/server/table/Server/DungeonScaleStat.xml +// Per-dungeon counterpart of the scale* attributes in npc Stat. No dungeonroom column references it. +[XmlRoot("ms2")] +public class DungeonScaleStatRoot { + [XmlElement] public List DungeonScaleStat; +} + +public partial class DungeonScaleStat { + [XmlAttribute] public int dungeonScaleStatID; + [XmlAttribute] public float scaleStatRate; + [XmlAttribute] public long scaleBaseTap; + [XmlAttribute] public long scaleBaseDef; + [XmlAttribute] public float scaleBaseSpaRate; +} diff --git a/Maple2.File.Tests/ServerTableParserTest.cs b/Maple2.File.Tests/ServerTableParserTest.cs index d2b415c..5a84fd0 100644 --- a/Maple2.File.Tests/ServerTableParserTest.cs +++ b/Maple2.File.Tests/ServerTableParserTest.cs @@ -513,5 +513,21 @@ public void TestDefaultCharacterInfo() { Assert.AreNotEqual(Vector3.Zero, hair.controls.control[0].position); Assert.AreNotEqual(Vector3.Zero, hair.controls.control[0].rotation); } + + [TestMethod] + public void TestDungeonScaleStat() { + var parser = new ServerTableParser(TestUtils.ServerReader); + + Dictionary scaleStats = parser.ParseDungeonScaleStat() + .ToDictionary(entry => entry.Id, entry => entry.ScaleStat); + + // Retail row: + DungeonScaleStat row = scaleStats[6001]; + Assert.AreEqual(8.0f, row.scaleStatRate); + Assert.AreEqual(1911810L, row.scaleBaseTap); + Assert.AreEqual(2552L, row.scaleBaseDef); + Assert.AreEqual(0.35f, row.scaleBaseSpaRate); + } }