Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Maple2.File.Parser/Maple2.File.Parser.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageTags>MapleStory2, File, Parser, m2d, xml</PackageTags>
<!-- Use following lines to write the generated files to disk. -->
<EmitCompilerGeneratedFiles Condition=" '$(Configuration)' == 'Debug' ">true</EmitCompilerGeneratedFiles>
<PackageVersion>2.4.35</PackageVersion>
<PackageVersion>2.4.36</PackageVersion>
<TargetFramework>net8.0</TargetFramework>
<PackageReadmeFile>README.md</PackageReadmeFile>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
13 changes: 13 additions & 0 deletions Maple2.File.Parser/ServerTableParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class ServerTableParser {
private readonly XmlSerializer groupSpawnSerializer;
private readonly XmlSerializer shadowExpeditionDropSerializer;
private readonly XmlSerializer additionalEffectGroupSerializer;
private readonly XmlSerializer globalEquipmentRankProbabilitySerializer;
private readonly XmlSerializer fishSerializer;
private readonly XmlSerializer fishingSpotSerializer;
private readonly XmlSerializer fishLureSerializer;
Expand Down Expand Up @@ -95,6 +96,7 @@ public ServerTableParser(M2dReader xmlReader) {
groupSpawnSerializer = new XmlSerializer(typeof(GroupSpawnRoot));
shadowExpeditionDropSerializer = new XmlSerializer(typeof(ShadowExpeditionDropRoot));
additionalEffectGroupSerializer = new XmlSerializer(typeof(AdditionalEffectGroupRoot));
globalEquipmentRankProbabilitySerializer = new XmlSerializer(typeof(GlobalEquipmentRankProbabilityRoot));
fishSerializer = new XmlSerializer(typeof(FishRoot));
fishingSpotSerializer = new XmlSerializer(typeof(FishingSpotRoot));
fishLureSerializer = new XmlSerializer(typeof(FishLureRoot));
Expand Down Expand Up @@ -537,6 +539,17 @@ public IEnumerable<ShadowExpeditionDrop> ParseShadowExpeditionDrop() {
}
}

public IEnumerable<GlobalEquipmentRankProbability> ParseGlobalEquipmentRankProbability() {
string xml = Sanitizer.RemoveEmpty(xmlReader.GetString(xmlReader.GetEntry("table/Server/globalEquipmentRankProbability.xml")));
var reader = XmlReader.Create(new StringReader(xml));
var data = globalEquipmentRankProbabilitySerializer.Deserialize(reader) as GlobalEquipmentRankProbabilityRoot;
Debug.Assert(data != null);

foreach (GlobalEquipmentRankProbability row in data.DropProbability) {
yield return row;
}
}

public IEnumerable<(int Id, Fish Fish)> ParseFish() {
string xml = Sanitizer.RemoveEmpty(xmlReader.GetString(xmlReader.GetEntry("table/Server/fish.xml")));
var reader = XmlReader.Create(new StringReader(xml));
Expand Down
4 changes: 3 additions & 1 deletion Maple2.File.Parser/Xml/Item/Property.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ namespace Maple2.File.Parser.Xml.Item;

public partial class Property {
[XmlAttribute] public bool skin;
[XmlAttribute] public short rank = 1;
// 0 when the item names no rank, as the client's CItemData starts it. A drop then rolls the rank from
// globalEquipmentRankProbability.xml, so 0 must stay apart from an explicit rank="1".
[XmlAttribute] public short rank;
[XmlAttribute] public int skinType;
[XmlAttribute] public int slotMax = 1; // if > 99999, set to 99999
[XmlAttribute] public int type;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Xml.Serialization;
using M2dXmlGenerator;

namespace Maple2.File.Parser.Xml.Table.Server;

// ./data/server/table/Server/globalEquipmentRankProbability.xml
// The rank an item drops at when its drop row names no grade and the item has no fixed rank. One row per
// npcRank, npcLevel and assistCondition; KR and CN override some npcRank 4 rows.
[XmlRoot("ms2")]
public partial class GlobalEquipmentRankProbabilityRoot {
[M2dFeatureLocale(Name = "DropProbability", Selector = "npcRank|npcLevel|assistCondition")]
private IList<GlobalEquipmentRankProbability> _dropProbability;
}

// The four columns are percentages of ranks 2 to 5; whatever they leave is rank 1. An empty cell is 0.
public partial class GlobalEquipmentRankProbability : IFeatureLocale {
[XmlAttribute] public int npcRank;
[XmlAttribute] public int npcLevel;
[XmlAttribute] public int assistCondition;
[XmlAttribute] public float Rare;
[XmlAttribute] public float Elite;
[XmlAttribute] public float Excellent;
[XmlAttribute] public float Legendary;
}
17 changes: 17 additions & 0 deletions Maple2.File.Tests/ItemParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ public void TestItemParser() {
Assert.AreEqual(35309, count);
}

[TestMethod]
public void TestItemRank() {
Filter.Load(TestUtils.XmlReader, Locale.NA.ToString(), "Live");
var parser = new ItemParser(TestUtils.XmlReader, "en");

Dictionary<int, short> ranks = parser.Parse()
.Where(entry => entry.Id is 11300106 or 13200172 or 11300001 or 11300003)
.ToDictionary(entry => entry.Id, entry => entry.Data.property.rank);

// 11300106 Apprentice Hat names no rank. 13200172's NA environment has <property rank="1">,
// 11300001 <property rank="2"> and 11300003 <property rank="3">.
Assert.AreEqual(0, ranks[11300106]);
Assert.AreEqual(1, ranks[13200172]);
Assert.AreEqual(2, ranks[11300001]);
Assert.AreEqual(3, ranks[11300003]);
}

[TestMethod]
public void TestItemParserNew() {
var locale = Locale.KR;
Expand Down
56 changes: 56 additions & 0 deletions Maple2.File.Tests/ServerTableParserTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Numerics;
using Maple2.File.Parser;
using Maple2.File.Parser.Enum;
using Maple2.File.Parser.Tools;
using Maple2.File.Parser.Xml.Table;
using Maple2.File.Parser.Xml.Table.Server;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -309,6 +310,61 @@ public void TestAdditionalEffectGroup() {
CollectionAssert.AreEqual(new short[] { 2 }, groups[1].value[3].effectLevel);
}

[TestMethod]
public void TestGlobalEquipmentRankProbability() {
var parser = new ServerTableParser(TestUtils.ServerReader);

Filter.Load(TestUtils.XmlReader, "NA", "Live");
Dictionary<(int, int, int), GlobalEquipmentRankProbability> na = parser.ParseGlobalEquipmentRankProbability()
.ToDictionary(row => (row.npcRank, row.npcLevel, row.assistCondition));
Filter.Load(TestUtils.XmlReader, "KR", "Live");
Dictionary<(int, int, int), GlobalEquipmentRankProbability> kr = parser.ParseGlobalEquipmentRankProbability()
.ToDictionary(row => (row.npcRank, row.npcLevel, row.assistCondition));
Filter.Load(TestUtils.XmlReader, "CN", "Live");
Dictionary<(int, int, int), GlobalEquipmentRankProbability> cn = parser.ParseGlobalEquipmentRankProbability()
.ToDictionary(row => (row.npcRank, row.npcLevel, row.assistCondition));

// 18 npcRanks x levels 1-99 x assistCondition 0/1. The KR and CN rows replace rows rather than add them.
Assert.AreEqual(18 * 99 * 2, na.Count);
Assert.AreEqual(18 * 99 * 2, kr.Count);
Assert.AreEqual(18 * 99 * 2, cn.Count);

// line 41: npcRank="1" npcLevel="20" assistCondition="0" Rare="16.5" Elite="" Excellent="" Legendary=""
AssertRow(na[(1, 20, 0)], 16.5f, 0f, 0f, 0f);

// line 437: npcRank="4" npcLevel="20" assistCondition="0" Rare="80" Elite="20", not its KR or CN override
AssertRow(na[(4, 20, 0)], 80f, 20f, 0f, 0f);
// line 635: the same key with locale="KR": Rare="100" Elite="0" Excellent="0"
AssertRow(kr[(4, 20, 0)], 100f, 0f, 0f, 0f);
// line 833: the same key with locale="CN": Rare="16.5"
AssertRow(cn[(4, 20, 0)], 16.5f, 0f, 0f, 0f);
// lines 488 and 686: npcRank="4" npcLevel="45" assistCondition="1", NA then KR
AssertRow(na[(4, 45, 1)], 80f, 20f, 0f, 0f);
AssertRow(kr[(4, 45, 1)], 0f, 50f, 50f, 0f);
// KR overrides only npcRank 4.
AssertRow(kr[(1, 20, 0)], 16.5f, 0f, 0f, 0f);

// line 1013: npcRank="6" npcLevel="11" assistCondition="0" Rare="16" Elite="83.99999" Excellent="1E-05" Legendary="0"
AssertRow(na[(6, 11, 0)], 16f, 83.99999f, 1E-05f, 0f);
// lines 1051 and 1052: assistCondition 0 and 1 differ for npcRank="6" npcLevel="30"
AssertRow(na[(6, 30, 0)], 0f, 99.99999f, 1E-05f, 0f);
AssertRow(na[(6, 30, 1)], 0f, 0f, 100f, 0f);

// line 1229: npcRank="7" npcLevel="20" assistCondition="0" Rare="16.52" Elite="0.00826446280991736" Excellent="0.000208"
AssertRow(na[(7, 20, 0)], 16.52f, 0.00826446280991736f, 0.000208f, 0f);
// line 1873: npcRank="90" npcLevel="45" assistCondition="0" Rare="93.8999" Elite="6" Excellent="0.1" Legendary="0.0001"
AssertRow(na[(90, 45, 0)], 93.8999f, 6f, 0.1f, 0.0001f);
// line 3962, the last row: npcRank="920" npcLevel="99" assistCondition="1" Excellent="100"
AssertRow(na[(920, 99, 1)], 0f, 0f, 100f, 0f);
}

private static void AssertRow(GlobalEquipmentRankProbability row, float rare, float elite, float excellent, float legendary) {
Assert.AreEqual(rare, row.Rare);
Assert.AreEqual(elite, row.Elite);
Assert.AreEqual(excellent, row.Excellent);
Assert.AreEqual(legendary, row.Legendary);
}

[TestMethod]
public void TestSpawnInteractObject() {
var parser = new ServerTableParser(TestUtils.ServerReader);
Expand Down
Loading