Skip to content

Commit edabcd9

Browse files
authored
Remove hardcoded server constants and use parsed constants from Server.m2d / constants.xml (#661)
* Add Constants WIP * Organize and clean-up old constants values WIP. Start using new server constants that are processed during file ingest WIP. * Continue switching hard coded constants to server constants parsed during file ingest WIP * Continue switching hard coded constants to server constants pared during file ingest #2 WIP * Continue switching hard coded constants to server constants pared during file ingest #3 WIP * Fix last hard coded constant value in PartyManager. Add in input cleaning and non-IConvertible data types handling during constant parsing. * Remove hard coded constants and utilize server constants within Inventory Manager. * Update the constant parsing to make it more efficient by removing the nested foreach loop and indexing the property names to search them later when looping through the parsed XML constants data. * Correct some misspelt property names in ConstantsTable record to allow them to retrieve their values. * Add Xml.m2d constants.xml within the Server.m2d constants.xml parsing for ConstantsTable. * Fix a crash at character entering world due to JSON deserialization confusion from two constructors and nethier defined as the JSON constructor. * Fix changes that were deleted during merge conflict resolution. Added back GlobalCubeSkillIntervalTime and merged into XML parsing functionality, while also adding back NpcMetadataCorpse parameter in NpcMetadata record. * Took CodeRabbit suggestion since it pointed out unreachable branches and it's much cleaner than what was being used to pass constant values for lasting sight radius, height up, and height down. * Remove accidental default values from hardcoded constants in ConstantsTable record pointed out by CodeRabbit. * Fix a mismatch of currency being used and currency error message being used found by CodeRabbit. * Replace Parse calls with TryParse to prevent runtime crashes. Additionally, remove Convert.ChangeType usage, create a reflection-based approach to access TryParse for generic objects. * Changes per feedback from Zin * Changes per feedback from Zin #2 * Correct NpcLastSight* constant values after adding them back from feedback. * Update per Zin's feedback round 3 * Revert removal of statLimits from ConfigManager, it is used in two places other than just the initialization of StatAttributes. * Remove the last unneccessary ConstantsTable parameter and fix some odd line breaks per Tadeucci's feedback * Fix mistake on unncessary ConstantsTable parameter removal * Run dotnet format * Fix dotnet format fail * Make adjustments based off coderabbitai suggestions.
1 parent 03ac847 commit edabcd9

55 files changed

Lines changed: 1583 additions & 987 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Maple2.Database/Model/Mail.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ internal class Mail {
5555

5656
[return: NotNullIfNotNull(nameof(other))]
5757
public static implicit operator Maple2.Model.Game.Mail?(Mail? other) {
58-
return other == null ? null : new Maple2.Model.Game.Mail {
58+
return other == null ? null : new Maple2.Model.Game.Mail() {
5959
ReceiverId = other.ReceiverId,
6060
Id = other.Id,
6161
SenderId = other.SenderId,

Maple2.Database/Storage/Game/GameStorage.Map.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ public IList<PlotCube> LoadCubesForOwner(long ownerId) {
111111
return Context.TrySaveChanges() ? ToPlotInfo(model) : null;
112112
}
113113

114-
public PlotInfo? GetSoonestPlotFromExpire() {
114+
public PlotInfo? GetSoonestPlotFromExpire(TimeSpan ugcHomeSaleWaitingTime) {
115115
IQueryable<UgcMap> maps = Context.UgcMap.Where(map => map.ExpiryTime > DateTimeOffset.MinValue && !map.Indoor);
116116
foreach (UgcMap map in maps) {
117117
if (map.OwnerId == 0) {
118-
map.ExpiryTime = map.ExpiryTime.Add(Constant.UgcHomeSaleWaitingTime);
118+
map.ExpiryTime = map.ExpiryTime.Add(ugcHomeSaleWaitingTime);
119119
}
120120
}
121121
UgcMap? model = maps.OrderBy(map => map.ExpiryTime).FirstOrDefault();

Maple2.Database/Storage/Metadata/ServerTableMetadataStorage.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class ServerTableMetadataStorage {
2929
private readonly Lazy<CombineSpawnTable> combineSpawnTable;
3030
private readonly Lazy<EnchantOptionTable> enchantOptionTable;
3131
private readonly Lazy<UnlimitedEnchantOptionTable> unlimitedEnchantOptionTable;
32+
private readonly Lazy<ConstantsTable> constantsTable;
3233

3334
public InstanceFieldTable InstanceFieldTable => instanceFieldTable.Value;
3435
public ScriptConditionTable ScriptConditionTable => scriptConditionTable.Value;
@@ -53,6 +54,7 @@ public class ServerTableMetadataStorage {
5354
public CombineSpawnTable CombineSpawnTable => combineSpawnTable.Value;
5455
public EnchantOptionTable EnchantOptionTable => enchantOptionTable.Value;
5556
public UnlimitedEnchantOptionTable UnlimitedEnchantOptionTable => unlimitedEnchantOptionTable.Value;
57+
public ConstantsTable ConstantsTable => constantsTable.Value;
5658

5759
public ServerTableMetadataStorage(MetadataContext context) {
5860
instanceFieldTable = Retrieve<InstanceFieldTable>(context, ServerTableNames.INSTANCE_FIELD);
@@ -78,6 +80,7 @@ public ServerTableMetadataStorage(MetadataContext context) {
7880
combineSpawnTable = Retrieve<CombineSpawnTable>(context, ServerTableNames.COMBINE_SPAWN);
7981
enchantOptionTable = Retrieve<EnchantOptionTable>(context, ServerTableNames.ENCHANT_OPTION);
8082
unlimitedEnchantOptionTable = Retrieve<UnlimitedEnchantOptionTable>(context, ServerTableNames.UNLIMITED_ENCHANT_OPTION);
83+
constantsTable = Retrieve<ConstantsTable>(context, ServerTableNames.CONSTANTS);
8184
}
8285

8386
public IEnumerable<GameEvent> GetGameEvents() {

Maple2.File.Ingest/Mapper/ServerTableMapper.cs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using System.Globalization;
2-
using System.Xml;
3-
using Maple2.Database.Extensions;
1+
using Maple2.Database.Extensions;
42
using Maple2.File.Ingest.Utils;
53
using Maple2.File.IO;
64
using Maple2.File.Parser;
75
using Maple2.File.Parser.Enum;
6+
using Maple2.File.Parser.Flat.Convert;
7+
using Maple2.File.Parser.Xml.Table;
88
using Maple2.File.Parser.Xml.Table.Server;
99
using Maple2.Model;
1010
using Maple2.Model.Common;
@@ -13,6 +13,11 @@
1313
using Maple2.Model.Game;
1414
using Maple2.Model.Game.Shop;
1515
using Maple2.Model.Metadata;
16+
using Newtonsoft.Json.Linq;
17+
using System.Globalization;
18+
using System.Numerics;
19+
using System.Reflection;
20+
using System.Xml;
1621
using DayOfWeek = System.DayOfWeek;
1722
using ExpType = Maple2.Model.Enum.ExpType;
1823
using Fish = Maple2.File.Parser.Xml.Table.Server.Fish;
@@ -128,6 +133,10 @@ protected override IEnumerable<ServerTableMetadata> Map() {
128133
Name = ServerTableNames.UNLIMITED_ENCHANT_OPTION,
129134
Table = ParseUnlimitedEnchantOption(),
130135
};
136+
yield return new ServerTableMetadata {
137+
Name = ServerTableNames.CONSTANTS,
138+
Table = ParseConstants(),
139+
};
131140

132141
}
133142

@@ -2135,4 +2144,56 @@ void AddSpecial(Dictionary<SpecialAttribute, int> values, Dictionary<SpecialAttr
21352144
}
21362145
}
21372146
}
2147+
2148+
private ConstantsTable ParseConstants() {
2149+
var constants = new ConstantsTable();
2150+
Dictionary<string, PropertyInfo> propertyLookup = typeof(ConstantsTable).GetProperties()
2151+
.ToDictionary(p => p.Name.Trim(), p => p, StringComparer.OrdinalIgnoreCase);
2152+
foreach ((string key, Constants.Key constant) in parser.ParseConstants()) {
2153+
string trimmedKey = key.Trim();
2154+
if (!propertyLookup.TryGetValue(trimmedKey, out PropertyInfo? property)) continue;
2155+
string cleanValue = CleanInput(constant.value.Trim(), trimmedKey, property.PropertyType);
2156+
GenericHelper.SetValue(property, constants, cleanValue);
2157+
}
2158+
return constants;
2159+
2160+
string CleanInput(string input, string propName, Type type) {
2161+
// check if string contains the ending 'f' for float designation, strip it if it does.
2162+
if (type == typeof(float) && input.Contains('f')) {
2163+
input = input.TrimEnd('f', 'F');
2164+
}
2165+
if (type == typeof(bool)) {
2166+
// 1 does not automatically equate to true during bool conversion
2167+
if (input == "1") {
2168+
input = "true";
2169+
}
2170+
// 0 does not automatically equate to false during bool conversion
2171+
if (input == "0") {
2172+
input = "false";
2173+
}
2174+
}
2175+
if (type == typeof(TimeSpan)) {
2176+
// Special case - dashes (-) are used instead of colons (:)
2177+
if (propName == "DailyTrophyResetDate") {
2178+
input = input.Replace('-', ':');
2179+
}
2180+
// Stored as 0.1 for 100ms
2181+
if (propName == "GlobalCubeSkillIntervalTime") {
2182+
input = $"0:0:{input}";
2183+
}
2184+
// Stored as an int value, convert to friendly input string for TimeSpan parsing
2185+
if (propName == "UgcHomeSaleWaitingTime") {
2186+
int.TryParse(input, out int result);
2187+
input = TimeSpan.FromSeconds(result).ToString(); // TODO: may not be correct conversion to TimeSpan
2188+
}
2189+
}
2190+
if (type == typeof(int)) {
2191+
// Remove prefix 0 on integers since they do not convert properly
2192+
if (input.Length > 1 && input[0] == '0') {
2193+
input = input.Remove(0, 1);
2194+
}
2195+
}
2196+
return input;
2197+
}
2198+
}
21382199
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Globalization;
2+
using System.Numerics;
3+
using System.Reflection;
4+
5+
namespace Maple2.File.Ingest.Utils;
6+
7+
public static class GenericHelper {
8+
public static void SetValue(PropertyInfo prop, object? obj, object? value) {
9+
if (obj == null && value == null || value == null) return;
10+
HandleNonIConvertibleTypes(prop, ref value);
11+
if (value == null) return;
12+
if (typeof(IConvertible).IsAssignableFrom(prop.PropertyType)) {
13+
TryParseObject(prop.PropertyType, value, out object? result);
14+
prop.SetValue(obj, result);
15+
return;
16+
}
17+
prop.SetValue(obj, value);
18+
}
19+
20+
private static object? HandleNonIConvertibleTypes(PropertyInfo prop, ref object? value) {
21+
if (value == null) return value;
22+
// Handle TimeSpan type
23+
if (prop.PropertyType == typeof(TimeSpan)) {
24+
TimeSpan.TryParse((string) value, CultureInfo.InvariantCulture, out TimeSpan val);
25+
value = val;
26+
}
27+
// Handle array types (int[], short[], etc.)
28+
if (prop.PropertyType.IsArray) {
29+
var elementType = prop.PropertyType.GetElementType();
30+
if (elementType == null) return value;
31+
string[] segments = ((string) value).Split(',');
32+
Array destinationArray = Array.CreateInstance(elementType, segments.Length);
33+
for (int i = 0; i < segments.Length; i++) {
34+
if (TryParseObject(elementType, segments[i].Trim(), out object? parseResult)) {
35+
destinationArray.SetValue(parseResult ?? default, i);
36+
}else {
37+
destinationArray.SetValue(elementType.IsValueType ? Activator.CreateInstance(elementType) : null, i);
38+
}
39+
}
40+
value = destinationArray;
41+
}
42+
// Handle Vector3 type
43+
if (prop.PropertyType == typeof(Vector3)) {
44+
string[] parts = ((string) value).Split(',');
45+
bool parseXSuccess = float.TryParse(parts[0], CultureInfo.InvariantCulture, out float x);
46+
bool parseYSuccess = float.TryParse(parts[1], CultureInfo.InvariantCulture, out float y);
47+
bool parseZSuccess = float.TryParse(parts[2], CultureInfo.InvariantCulture, out float z);
48+
if (parts.Length != 3 || parseXSuccess && parseYSuccess && parseZSuccess) {
49+
value = Vector3.Zero;
50+
} else {
51+
value = new Vector3(x, y, z);
52+
}
53+
}
54+
return value;
55+
}
56+
57+
private static bool TryParseObject(Type? elementType, object? input, out object? result) {
58+
if (elementType == null || input == null) {
59+
result = null;
60+
return false;
61+
}
62+
63+
string inputString = Convert.ToString(input, CultureInfo.InvariantCulture)!;
64+
65+
// No TryParse method exists for a string, use the result directly.
66+
if (elementType == typeof(string)) {
67+
result = inputString;
68+
return true;
69+
}
70+
71+
Type[] argTypes = {
72+
typeof(string),
73+
typeof(IFormatProvider),
74+
elementType.MakeByRefType()
75+
};
76+
77+
var method = elementType.GetMethod("TryParse",
78+
BindingFlags.Public | BindingFlags.Static,
79+
null, argTypes, null);
80+
if (method != null) {
81+
object[] args = [inputString, CultureInfo.InvariantCulture, null!];
82+
bool success = (bool) method.Invoke(null, args)!;
83+
result = args[2];
84+
return success;
85+
}
86+
87+
// Fallback without CultureInfo provided, in case the type does not have a CultureInfo overload.
88+
Type[] simpleArgs = { typeof(string), elementType.MakeByRefType() };
89+
method = elementType.GetMethod("TryParse",
90+
BindingFlags.Public | BindingFlags.Static,
91+
null, simpleArgs, null);
92+
if (method != null) {
93+
object[] args = { inputString, null! };
94+
bool success = (bool) method.Invoke(null, args)!;
95+
result = args[1];
96+
return success;
97+
}
98+
99+
100+
result = null;
101+
return false;
102+
}
103+
}

Maple2.Model/Common/ServerTableNames.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ public static class ServerTableNames {
2424
public const string COMBINE_SPAWN = "combineSpawn*.xml";
2525
public const string ENCHANT_OPTION = "enchantOption.xml";
2626
public const string UNLIMITED_ENCHANT_OPTION = "unlimitedEnchantOption.xml";
27+
public const string CONSTANTS = "constants.xml";
2728
}

Maple2.Model/Game/Cube/Nurturing.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,20 @@ public Nurturing(long exp, short claimedGiftForStage, long[] playedBy, DateTimeO
4848
}
4949
}
5050

51-
public void Feed() {
51+
public void Feed(int nurturingEatGrowth) {
5252
if (Exp >= NurturingMetadata.RequiredGrowth.Last().Exp) {
5353
return;
5454
}
5555

56-
Exp += Constant.NurturingEatGrowth;
56+
Exp += nurturingEatGrowth;
5757
if (Exp >= NurturingMetadata.RequiredGrowth.First(x => x.Stage == Stage).Exp) {
5858
Stage++;
5959
}
6060
LastFeedTime = DateTimeOffset.Now;
6161
}
6262

63-
public bool Play(long accountId) {
64-
if (PlayedBy.Count >= Constant.NurturingPlayMaxCount) {
63+
public bool Play(long accountId, int nurturingEatGrowth, int nurturingPlayMaxCount) {
64+
if (PlayedBy.Count >= nurturingPlayMaxCount) {
6565
return false;
6666
}
6767

@@ -70,7 +70,7 @@ public bool Play(long accountId) {
7070
}
7171

7272
PlayedBy.Add(accountId);
73-
Feed();
73+
Feed(nurturingEatGrowth);
7474
return true;
7575
}
7676

Maple2.Model/Game/Mail.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Text;
22
using Maple2.Model.Enum;
3-
using Maple2.Model.Metadata;
43
using Maple2.PacketLib.Tools;
54
using Maple2.Tools;
65
using Maple2.Tools.Extensions;
@@ -35,11 +34,19 @@ public class Mail : IByteSerializable {
3534
// More than 1 item may not display properly
3635
public readonly IList<Item> Items;
3736

37+
// Specifically for Mail object cloning (Mail.cs:57)
3838
public Mail() {
3939
TitleArgs = new List<(string Key, string Value)>();
4040
ContentArgs = new List<(string Key, string Value)>();
4141
Items = new List<Item>();
42-
ExpiryTime = DateTimeOffset.UtcNow.AddDays(Constant.MailExpiryDays).ToUnixTimeSeconds();
42+
// ExpiryTime will be overwritten, no need to set it here with a parameter passing server constant value.
43+
}
44+
45+
public Mail(int mailExpiryDays) {
46+
TitleArgs = new List<(string Key, string Value)>();
47+
ContentArgs = new List<(string Key, string Value)>();
48+
Items = new List<Item>();
49+
ExpiryTime = DateTimeOffset.UtcNow.AddDays(mailExpiryDays).ToUnixTimeSeconds();
4350
}
4451

4552
public void Update(Mail other) {

Maple2.Model/Game/User/StatAttributes.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ public class StatAttributes : IByteSerializable {
1313
public int TotalPoints => Sources.Count;
1414
public int UsedPoints => Allocation.Count;
1515

16-
public StatAttributes() {
16+
public StatAttributes(IDictionary<string, int> statLimits) {
1717
Sources = new PointSources();
18-
Allocation = new PointAllocation();
18+
Allocation = new PointAllocation(statLimits);
1919
}
2020

2121
public void WriteTo(IByteWriter writer) {
@@ -52,14 +52,15 @@ public void WriteTo(IByteWriter writer) {
5252

5353
public class PointAllocation : IByteSerializable {
5454
private readonly Dictionary<BasicAttribute, int> points;
55+
private readonly IDictionary<string, int> statLimits;
5556

5657
public BasicAttribute[] Attributes => points.Keys.ToArray();
5758
public int Count => points.Values.Sum();
5859

5960
public int this[BasicAttribute type] {
6061
get => points.GetValueOrDefault(type);
6162
set {
62-
if (value < 0 || value > StatLimit(type)) {
63+
if (value < 0 || value > StatLimit(type, statLimits)) {
6364
return;
6465
}
6566
if (value == 0) {
@@ -71,19 +72,20 @@ public int this[BasicAttribute type] {
7172
}
7273
}
7374

74-
public PointAllocation() {
75+
public PointAllocation(IDictionary<string, int> statLimits) {
7576
points = new Dictionary<BasicAttribute, int>();
77+
this.statLimits = statLimits;
7678
}
7779

78-
public static int StatLimit(BasicAttribute type) {
80+
public static int StatLimit(BasicAttribute type, IDictionary<string, int> statLimits) {
7981
return type switch {
80-
BasicAttribute.Strength => Constant.StatPointLimit_str,
81-
BasicAttribute.Dexterity => Constant.StatPointLimit_dex,
82-
BasicAttribute.Intelligence => Constant.StatPointLimit_int,
83-
BasicAttribute.Luck => Constant.StatPointLimit_luk,
84-
BasicAttribute.Health => Constant.StatPointLimit_hp,
85-
BasicAttribute.CriticalRate => Constant.StatPointLimit_cap,
86-
_ => 0,
82+
BasicAttribute.Strength => statLimits.GetValueOrDefault("StatPointLimit_str"),
83+
BasicAttribute.Dexterity => statLimits.GetValueOrDefault("StatPointLimit_dex"),
84+
BasicAttribute.Intelligence => statLimits.GetValueOrDefault("StatPointLimit_int"),
85+
BasicAttribute.Luck => statLimits.GetValueOrDefault("StatPointLimit_luk"),
86+
BasicAttribute.Health => statLimits.GetValueOrDefault("StatPointLimit_hp"),
87+
BasicAttribute.CriticalRate => statLimits.GetValueOrDefault("StatPointLimit_cap"),
88+
_ => 0
8789
};
8890
}
8991

0 commit comments

Comments
 (0)