diff --git a/.gitignore b/.gitignore
index 8643ed9..ca30328 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@
*.csproj.user
.vscode/
+DataSourceTool/Properties/launchSettings.json
diff --git a/ChangeLog.md b/ChangeLog.md
index 100ddd0..1590f4e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
# Change log
+## 0.1.0.0 (??)
+- initial release for Monster Hunter World Iceborne armour sets.
+
+
## 0.0.9.7 (??)
- Improved search algorithm performance by 2 to 2.5.
diff --git a/DataSourceTool/DataSourceTool.csproj b/DataSourceTool/DataSourceTool.csproj
index c882ff9..7b64e9d 100644
--- a/DataSourceTool/DataSourceTool.csproj
+++ b/DataSourceTool/DataSourceTool.csproj
@@ -11,6 +11,7 @@
+
diff --git a/DataSourceTool/Exporter.cs b/DataSourceTool/Exporter.cs
index 40d9410..a52dfd1 100644
--- a/DataSourceTool/Exporter.cs
+++ b/DataSourceTool/Exporter.cs
@@ -22,7 +22,8 @@ public async Task Run(string[] args)
//var httpClient = new HttpClient();
//Task fetchWeaponsTask = httpClient.GetStringAsync("https://mhw-db.com/weapons?p={\"slug\":false,\"crafting\":false,\"assets\":false}");
- IDataSource source = new MHArmory.AthenaAssDataSource.DataSource(logger, null, null);
+ IDataSource source = new MHArmory.MhwDbDataSource.DataSource(logger, false);
+ //IDataSource source = new MHArmory.AthenaAssDataSource.DataSource(logger, null, null);
IList armorPieces = (await source.GetArmorPieces()).OrderBy(x => x.Id).ToList();
IList skills = (await source.GetSkills()).OrderBy(x => x.Id).ToList();
@@ -117,11 +118,20 @@ private object Export(IEnumerable craftMaterials)
});
}
+ //private object Export(IEnumerable abilities)
+ //{
+ // return abilities.Select((x, i) => new AbilityPrimitive
+ // {
+ // Id = i,
+ // Level = x.Level,
+ // Description = x.Description
+ // });
+ //}
private object Export(IEnumerable abilities)
{
- return abilities.Select((x, i) => new AbilityPrimitive
+ return abilities.Select(x => new AbilityPrimitive
{
- Id = i,
+ Id = x.Id,
Level = x.Level,
Description = x.Description
});
diff --git a/MHArmory.ArmoryDataSource/DataSource.cs b/MHArmory.ArmoryDataSource/DataSource.cs
index ea697fe..6877931 100644
--- a/MHArmory.ArmoryDataSource/DataSource.cs
+++ b/MHArmory.ArmoryDataSource/DataSource.cs
@@ -46,6 +46,19 @@ public Task GetCraftMaterials()
}
private IList armorSetSkills;
+ private Task armorSetSkillsTask;
+ public Task GetArmorSetSkills()
+ {
+ if (armorSetSkillsTask != null)
+ return armorSetSkillsTask;
+
+ FetchSkills();
+ FetchArmorSetSkills();
+
+ armorSetSkillsTask = Task.FromResult(armorSetSkills.ToArray());
+
+ return armorSetSkillsTask;
+ }
private void FetchArmorSetSkills()
{
diff --git a/MHArmory.AthenaAssDataSource/DataSource.cs b/MHArmory.AthenaAssDataSource/DataSource.cs
index 344e4a7..9a4b00c 100644
--- a/MHArmory.AthenaAssDataSource/DataSource.cs
+++ b/MHArmory.AthenaAssDataSource/DataSource.cs
@@ -810,6 +810,11 @@ private Dictionary LoadLocalizations(string file)
return result;
}
+
+ public Task GetArmorSetSkills()
+ {
+ throw new NotImplementedException();
+ }
}
internal static class AbiliyOperators
diff --git a/MHArmory.Core.WPF/ValueConverters/SlotToImageSourceValueConverter.cs b/MHArmory.Core.WPF/ValueConverters/SlotToImageSourceValueConverter.cs
index 9b57aa1..f3bf6ee 100644
--- a/MHArmory.Core.WPF/ValueConverters/SlotToImageSourceValueConverter.cs
+++ b/MHArmory.Core.WPF/ValueConverters/SlotToImageSourceValueConverter.cs
@@ -13,7 +13,7 @@ public class SlotToImageSourceValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value is int slotSize && slotSize >= 1 && slotSize <= 3 && parameter is string rasterSizeStr && int.TryParse(rasterSizeStr, out int rasterSize))
+ if (value is int slotSize && slotSize >= 1 && slotSize <= 4 && parameter is string rasterSizeStr && int.TryParse(rasterSizeStr, out int rasterSize))
return RasterizedImageContainer.GetRasterizedImage(rasterSize, $"Icons/Jewels/Jewel{slotSize}.svg");
return null;
diff --git a/MHArmory.Core/DataSources.cs b/MHArmory.Core/DataSources.cs
index 8c20ad1..146cc74 100644
--- a/MHArmory.Core/DataSources.cs
+++ b/MHArmory.Core/DataSources.cs
@@ -13,5 +13,6 @@ public interface IDataSource
Task GetSkills();
Task GetCharms();
Task GetJewels();
+ Task GetArmorSetSkills();
}
}
diff --git a/MHArmory.Core/DataStructures/Weapon.cs b/MHArmory.Core/DataStructures/Weapon.cs
index 0c0b11e..8cd9cd9 100644
--- a/MHArmory.Core/DataStructures/Weapon.cs
+++ b/MHArmory.Core/DataStructures/Weapon.cs
@@ -45,14 +45,16 @@ public class Weapon : IEquipment
public IAbility[] Abilities { get; }
public IEvent Event { get; }
public ICraftMaterial[] CraftMaterials { get; }
+ public IArmorSetSkill[] ArmorSetSkills { get; }
- public Weapon(int id, WeaponType weaponType, int[] slots, IAbility[] abilities, IEvent evt)
+ public Weapon(int id, WeaponType weaponType, int[] slots, IAbility[] abilities, IEvent evt, IArmorSetSkill[] armorSetSkills = null)
{
Id = id;
WeaponType = weaponType;
Slots = slots;
Abilities = abilities;
Event = evt;
+ ArmorSetSkills = armorSetSkills ?? Array.Empty();
}
public Weapon(int id, Dictionary name, WeaponType weaponType, int rarity, int[] slots, IAbility[] abilities, IEvent evt)
diff --git a/MHArmory.Core/HttpDataAccess.cs b/MHArmory.Core/HttpDataAccess.cs
index 80c8283..4d348fc 100644
--- a/MHArmory.Core/HttpDataAccess.cs
+++ b/MHArmory.Core/HttpDataAccess.cs
@@ -115,6 +115,9 @@ private string[] GetInvolvedFiles(string api)
private string ReadRawDataFromCache(string api, ILogger logger)
{
+ if (!hasWriteAccess)
+ return null;
+
string[] files;
try
diff --git a/MHArmory.Core/Localization.cs b/MHArmory.Core/Localization.cs
index bfe8d7f..82476b1 100644
--- a/MHArmory.Core/Localization.cs
+++ b/MHArmory.Core/Localization.cs
@@ -10,12 +10,12 @@ public static class Localization
public static readonly Dictionary AvailableLanguageCodes = new Dictionary
{
["EN"] = "English",
- ["FR"] = "Français",
- ["JP"] = "日本語",
- ["IT"] = "Italiano",
- ["DE"] = "Deutsch",
- ["KR"] = "한국어",
- ["CN"] = "中文繁體",
+ //["FR"] = "Français",
+ //["JP"] = "日本語",
+ //["IT"] = "Italiano",
+ //["DE"] = "Deutsch",
+ //["KR"] = "한국어",
+ //["CN"] = "中文繁體",
};
public const string DefaultLanguage = "EN";
diff --git a/MHArmory.MhwDbDataSource/DataSource.cs b/MHArmory.MhwDbDataSource/DataSource.cs
index 5fbb008..5f43754 100644
--- a/MHArmory.MhwDbDataSource/DataSource.cs
+++ b/MHArmory.MhwDbDataSource/DataSource.cs
@@ -15,6 +15,24 @@ public class DataSource : IDataSource
{
private readonly ILogger logger;
private readonly bool hasWriteAccess;
+ private readonly object loadLock = new object();
+ private readonly Dictionary localizationKeys = new Dictionary() { { "fr", "FR" }, { "de", "DE" }, { "zh", "CN" } };
+ private readonly string DB_KEY_SKILLS = "skills";
+ private readonly string DB_KEY_DECORATIONS = "decorations";
+ private readonly string DB_KEY_ARMORS = "armor";
+ private readonly string DB_KEY_ARMOR_SETS = "armor/sets";
+ private readonly string DB_KEY_CHARMS = "charms";
+ private readonly string DB_KEY_ITEMS = "items";
+
+ private Task loadTask;
+ private IAbility[] abilities;
+ private ISkill[] skills;
+ private IArmorPiece[] armors;
+ private ICharm[] charms;
+ private IJewel[] jewels;
+ private ILocalizedItem[] localizedItems;
+
+ public string Description { get; } = "MHW-DB";
public DataSource(ILogger logger, bool hasWriteAccess)
{
@@ -22,78 +40,68 @@ public DataSource(ILogger logger, bool hasWriteAccess)
this.hasWriteAccess = hasWriteAccess;
}
- public async Task GetAbilities()
+ private async Task load()
{
- if (loadTask == null)
- loadTask = LoadData(null);
-
+ lock (loadLock)
+ {
+ if (loadTask == null)
+ loadTask = LoadData();
+ }
await loadTask;
+ }
+ public async Task GetAbilities()
+ {
+ await load();
return abilities;
}
public async Task GetSkills()
{
- if (loadTask == null)
- loadTask = LoadData(null);
-
- await loadTask;
-
+ await load();
return skills;
}
public async Task GetArmorPieces()
{
- if (loadTask == null)
- loadTask = LoadData(null);
-
- await loadTask;
-
+ await load();
return armors;
}
public async Task GetCharms()
{
- if (loadTask == null)
- loadTask = LoadData(null);
-
- await loadTask;
-
+ await load();
return charms;
}
public async Task GetJewels()
{
- if (loadTask == null)
- loadTask = LoadData(null);
-
- await loadTask;
-
+ await load();
return jewels;
}
- private Task loadTask;
-
- private IAbility[] abilities;
- private ISkill[] skills;
- private IArmorPiece[] armors;
- private ICharm[] charms;
- private IJewel[] jewels;
+ public async Task GetCraftMaterials()
+ {
+ await load();
+ return localizedItems;
+ }
- public string Description { get; } = "http://mhw-db.com";
- private async Task LoadData(ILogger logger)
+ private async Task LoadData()
{
- await LoadSkillsData(logger);
+ await Task.WhenAll(
+ LoadSkillsData(),
+ LoadItemData()
+ );
- await Task.WhenAll( // <- this must be called after LoadSkillsData
- LoadArmorsData(logger),
- LoadCharmsData(logger),
- LoadJewelsData(logger)
+ await Task.WhenAll( // <- this must be called after LoadSkillsData and LoadItemData
+ LoadArmorsData(),
+ LoadCharmsData(),
+ LoadJewelsData()
);
}
- private async Task> LoadBase(string api, ILogger logger)
+ private async Task> LoadBase(string api)
{
string content;
@@ -128,297 +136,337 @@ private async Task> LoadBase(string api, ILogger logger)
return null;
}
- private async Task LoadArmorsData(ILogger logger)
+ private async Task LoadItemData()
{
- Task> armorTask = LoadBase("armor", logger);
- Task> setsTask = LoadBase("armor/sets", logger);
-
- await Task.WhenAll(armorTask, setsTask);
+ Task> itemTask = LoadBase(DB_KEY_ITEMS);
+ var itemLocalizations = new Dictionary>>();
+ foreach (KeyValuePair localization in localizationKeys)
+ {
+ itemLocalizations[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_ITEMS);
+ }
- IList armorResult = armorTask.Result;
- IList setsResult = setsTask.Result;
+ IList itemResult = await itemTask;
- if (armorResult == null || setsResult == null)
+ if (itemResult == null)
+ {
+ logger?.LogError("Query for all items failed");
return;
+ }
- var allArmors = new IArmorPiece[armorResult.Count];
-
- for (int i = 0; i < allArmors.Length; i++)
+ var allItems = new Dictionary();
+ foreach(ItemPrimitive item in itemResult)
{
- var localAbilities = new IAbility[armorResult[i].Abilities.Count];
+ allItems[item.Id] = new DataStructures.LocalizedItem(item);
+ }
- for (int j = 0; j < localAbilities.Length; j++)
+ foreach (KeyValuePair>> localizationQuery in itemLocalizations)
+ {
+ IList localization = await localizationQuery.Value;
+ if (localization == null)
{
- int skillId = armorResult[i].Abilities[j].SkillId;
- int abilityLevel = armorResult[i].Abilities[j].Level;
+ logger?.LogError($"Query for item localization '{localizationQuery.Key}' failed");
+ continue;
+ }
+ foreach (ItemPrimitive item in localization)
+ {
+ allItems[item.Id].AddLocalization(localizationQuery.Key, item);
+ }
+ }
- ISkill skill = skills.FirstOrDefault(s => s.Id == skillId);
- IAbility ability = abilities.FirstOrDefault(a => a.Skill.Id == skillId && a.Level == abilityLevel);
+ localizedItems = allItems.Values.ToArray();
+ }
- localAbilities[j] = new Ability(skill, abilityLevel, ability.Description);
- }
+ private async Task LoadArmorsData()
+ {
+ Task> armorTask = LoadBase(DB_KEY_ARMORS);
+ var armorLocalizations = new Dictionary>>();
+ foreach (KeyValuePair localization in localizationKeys)
+ {
+ armorLocalizations[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_ARMORS);
+ }
+ Task> setsTask = LoadBase(DB_KEY_ARMOR_SETS);
+ var armorSetLocalizations = new Dictionary>>();
+ foreach (KeyValuePair localization in localizationKeys)
+ {
+ armorSetLocalizations[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_ARMOR_SETS);
+ }
+
+ IList armorResult = await armorTask;
- allArmors[i] = new DataStructures.ArmorPiece(armorResult[i], localAbilities);
+ if (armorResult == null)
+ {
+ logger?.LogError("Query for all armors failed");
+ return;
}
- foreach (ArmorSetPrimitive armorSetPrimitive in setsResult)
+ var allArmors = new Dictionary();
+
+ foreach(ArmorPiecePrimitive primitive in armorResult)
+ {
+ IAbility[] localAbilities = primitive.Abilities.Select(x => GetAbility(x.SkillId, x.Level)).ToArray();
+ if (allArmors.ContainsKey(primitive.Id))
+ throw new InvalidOperationException($"Armor identifier with ID '{primitive.Id}' and Name '{primitive.Name}' is a duplicate");
+ else
+ allArmors[primitive.Id] = new DataStructures.ArmorPiece(primitive, localAbilities, GetCraftingMaterials(primitive.Crafting.Materials));
+ }
+
+ foreach (KeyValuePair>> localizationQuery in armorLocalizations)
{
- var setArmorPieces = new IArmorPiece[armorSetPrimitive.ArmorPieces.Length];
- for (int i = 0; i < armorSetPrimitive.ArmorPieces.Length; i++)
- setArmorPieces[i] = allArmors.FirstOrDefault(x => x.Id == armorSetPrimitive.ArmorPieces[i].ArmorPieceId);
+ IList localization = await localizationQuery.Value;
+ if (localization == null)
+ {
+ logger?.LogError($"Query for armor localization '{localizationQuery.Key}' failed");
+ continue;
+ }
+ foreach (ArmorPiecePrimitive armor in localization)
+ {
+ allArmors[armor.Id].AddLocalization(localizationQuery.Key, armor);
+ }
+ }
- List armorSetSkills = null;
+ IList setsResult = await setsTask;
+ if(setsResult == null)
+ {
+ logger?.LogError("Query for all armor sets failed");
+ return;
+ }
+ var allArmorSetBoni = new Dictionary>>();
+ int armorSetSkillpartId = 0;
+ foreach (ArmorSetPrimitive armorSetPrimitive in setsResult)
+ {
+ var setArmorPieces = new List();
+ foreach(ArmorPieceIdPrimitive primitive in armorSetPrimitive.ArmorPieces)
+ {
+ DataStructures.ArmorPiece armor = allArmors[primitive.ArmorPieceId];
+ armor.Id = armorSetPrimitive.Id; // The UI later groups the armors by their ID
+ setArmorPieces.Add(armor);
+ }
+
if (armorSetPrimitive.Bonus != null)
{
- armorSetSkills = new List();
+ var armorSetSkills = new List();
foreach (ArmorSetBonusRankPrimitive bonusRank in armorSetPrimitive.Bonus.Ranks)
{
IAbility[] setAbilities = abilities.Where(a => a.Skill.Id == bonusRank.Skill.SkillId && a.Level == bonusRank.Skill.Level).ToArray();
- armorSetSkills.Add(new ArmorSetSkill(bonusRank.PieceCount, setAbilities));
+ armorSetSkills.Add(new ArmorSetSkillPart(armorSetSkillpartId, bonusRank.PieceCount, setAbilities));
+ ++armorSetSkillpartId;
}
+ var armorSetBonus = new ArmorSetBonus(armorSetPrimitive.Bonus, armorSetSkills.ToArray());
+ allArmorSetBoni[armorSetPrimitive.Id] = new Tuple>(armorSetBonus, setArmorPieces);
}
- var armorSet = new ArmorSet(armorSetPrimitive.Id, false, setArmorPieces, armorSetSkills?.ToArray());
+ // Infos about complete Armor sets can not be found in the MWH-DB
+ //var armorSet = new FullArmorSet(armorSetPrimitive.Id, setArmorPieces.ToArray());
- foreach (DataStructures.ArmorPiece armorPiece in setArmorPieces)
- armorPiece.UpdateArmorSet(armorSet);
+ //foreach (DataStructures.ArmorPiece armorPiece in setArmorPieces)
+ // armorPiece.FullArmorSet = armorSet;
+ }
+ foreach(KeyValuePair>> task in armorSetLocalizations)
+ {
+ IList localization = await task.Value;
+ if (localization == null)
+ {
+ logger?.LogError($"Query for armor sets localization '{task.Key}' failed");
+ continue;
+ }
+ foreach (ArmorSetPrimitive armorSet in localization)
+ {
+ if (armorSet.Bonus == null)
+ continue;
+ if (allArmorSetBoni.ContainsKey(armorSet.Id))
+ allArmorSetBoni[armorSet.Id].Item1.AddLocalization(task.Key, armorSet.Bonus);
+ else
+ logger?.LogError($"Armor set with ID '{armorSet.Id}' in language '{task.Key}' does not exist in the original");
+ }
+ }
+ foreach(KeyValuePair>> armorSetBonus in allArmorSetBoni)
+ {
+ foreach(DataStructures.ArmorPiece armor in armorSetBonus.Value.Item2)
+ {
+ var boni = new IArmorSetSkill[1];
+ boni[0] = armorSetBonus.Value.Item1;
+ armor.UpdateArmorSetBoni(boni);
+ }
}
- armors = allArmors;
+ armors = allArmors.Values.ToArray();
}
- private async Task LoadSkillsData(ILogger logger)
+ private async Task LoadSkillsData()
{
- IList result = await LoadBase("skills", logger);
+ Task> query = LoadBase(DB_KEY_SKILLS);
+ var localizationQueries = new Dictionary>>();
+ foreach(KeyValuePair localization in localizationKeys)
+ {
+ localizationQueries[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_SKILLS);
+ }
+
+
+ var allSkills = new Dictionary();
+ IList result = await query;
if (result == null)
+ {
+ logger?.LogError("Query for all skills failed");
return;
+ }
+ foreach (SkillPrimitive skillPrimitive in result)
+ {
+ var skill = new DataStructures.Skill(skillPrimitive);
- var allAbilities = new HashSet();
- var allSkills = new ISkill[result.Count];
- int localSkillCount = 0;
+ if(allSkills.ContainsKey(skill.Id))
+ logger?.LogError($"Skill identifier with ID '{skill.Id}' and Name '{skill.Name}' is a duplicate");
+ else
+ allSkills[skill.Id] = skill;
+ }
- foreach (SkillPrimitive skillPrimitive in result)
+ foreach(KeyValuePair>> localizationQuery in localizationQueries)
{
- var skill = new DataStructures.Skill(skillPrimitive.Id, skillPrimitive.Name, skillPrimitive.Description);
- var localAbilities = new IAbility[skillPrimitive.Abilities.Count];
- int localAbilityCount = 0;
+ IList localization = await localizationQuery.Value;
+ if(localization == null)
+ {
+ logger?.LogError($"Query for skills localization '{localizationQuery.Key}' failed");
+ continue;
+ }
+ foreach(SkillPrimitive skill in localization)
+ {
+ allSkills[skill.Id].AddLocalization(localizationQuery.Key, skill);
+ }
+ }
- foreach (AbilityPrimitive abilityPrimitive in skillPrimitive.Abilities)
+ skills = allSkills.Values.ToArray();
+ var allAbilities = new HashSet();
+ foreach (ISkill skill in skills)
+ {
+ foreach (DataStructures.Ability ability in skill.Abilities)
{
- var ability = new Ability(skill, abilityPrimitive.Level, abilityPrimitive.Description);
if (allAbilities.Add(ability) == false)
logger?.LogError($"Ability identifier 'skill {ability.Skill.Name} level {ability.Level}' is a duplicate");
- localAbilities[localAbilityCount++] = ability;
}
-
- skill.SetAbilities(localAbilities);
-
- allSkills[localSkillCount++] = skill;
}
-
abilities = allAbilities.ToArray();
- skills = allSkills;
}
- private async Task LoadCharmsData(ILogger logger)
+ private async Task LoadCharmsData()
{
- IList result = await LoadBase("charms", logger);
+ var localizationQueries = new Dictionary>>();
+ Task> query = LoadBase(DB_KEY_CHARMS);
+ foreach (KeyValuePair localization in localizationKeys)
+ {
+ localizationQueries[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_CHARMS);
+ }
- if (result == null)
- return;
- var localCharms = new ICharm[result.Count];
+ IList result = await query;
- for (int i = 0; i < localCharms.Length; i++)
+ if (result == null)
{
- CharmPrimitive currentCharmPrimitive = result[i];
-
- var charmLevels = new DataStructures.CharmLevel[currentCharmPrimitive.Levels.Count];
+ logger?.LogError("Query for all charms failed");
+ return;
+ }
+ var localCharms = new Dictionary();
+ int currentCharmLevelId = 0;
+ foreach(CharmPrimitive charmPrimitive in result)
+ {
+ var charm = new DataStructures.Charm(charmPrimitive);
+ var charmLevels = new DataStructures.CharmLevel[charmPrimitive.Levels.Count];
for (int j = 0; j < charmLevels.Length; j++)
{
- IAbility[] localAbilities = currentCharmPrimitive.Levels[j].Abilitites.Select(x => CreateAbility(x.SkillId, x.Level)).ToArray();
- charmLevels[j] = new DataStructures.CharmLevel(i + 1, currentCharmPrimitive.Levels[j], localAbilities);
+ IAbility[] localAbilities = charmPrimitive.Levels[j].Abilitites.Select(x => GetAbility(x.SkillId, x.Level)).ToArray();
+ charmLevels[j] = new DataStructures.CharmLevel(currentCharmLevelId, charmPrimitive.Levels[j], localAbilities, GetCraftingMaterials(charmPrimitive.Levels[j].Crafting.Materials));
+ ++currentCharmLevelId;
}
-
- localCharms[i] = new Charm(i + 1, currentCharmPrimitive.Name, charmLevels);
+ charm.SetCharmLevels(charmLevels);
+ if (localCharms.ContainsKey(charm.Id))
+ logger?.LogError($"Charm identifier with ID '{charm.Id}' and Name '{charm.Name}' is a duplicate");
+ else
+ localCharms[charm.Id] = charm;
}
- charms = localCharms;
- }
-
- private async Task LoadJewelsData(ILogger logger)
- {
- IList result = await LoadBase("decorations", logger);
-
- if (result == null)
- return;
-
- var localJewels = new DataStructures.Jewel[result.Count];
- for (int i = 0; i < localJewels.Length; i++)
+ foreach (KeyValuePair>> localizationQuery in localizationQueries)
{
- IAbility[] localAbilities = result[i].Abilitites.Select(a => CreateAbility(a.SkillId, a.Level)).ToArray();
- localJewels[i] = new DataStructures.Jewel(result[i], localAbilities);
+ IList localization = await localizationQuery.Value;
+ if (localization == null)
+ {
+ logger?.LogError($"Query for charms localization '{localizationQuery.Key}' failed");
+ continue;
+ }
+ foreach (CharmPrimitive charm in localization)
+ {
+ localCharms[charm.Id].AddLocalization(localizationQuery.Key, charm);
+ }
}
- jewels = localJewels;
+ charms = localCharms.Values.ToArray();
}
- private Ability CreateAbility(int skillId, int level)
+ private async Task LoadJewelsData()
{
- IAbility localAbility = abilities.FirstOrDefault(a => a.Skill.Id == skillId && a.Level == level);
- ISkill localSkill = skills.FirstOrDefault(s => s.Id == skillId);
-
- return new Ability(localSkill, level, localAbility.Description);
- }
-
- // ***** Conversion methods *****
-
- public void ConvertArmorPieces(IEnumerable armorPieces, string outputFilename)
- {
- ConvertMany(armorPieces, outputFilename, ConvertArmorPiece);
- }
-
- public void ConvertSkills(IEnumerable skills, string outputFilename)
- {
- ConvertMany(skills, outputFilename, ConvertSkill);
- }
-
- public void ConvertCharms(IEnumerable charms, string outputFilename)
- {
- ConvertMany(charms, outputFilename, ConvertCharm);
- }
-
- public void ConvertJewels(IEnumerable jewels, string outputFilename)
- {
- ConvertMany(jewels, outputFilename, ConvertJewel);
- }
-
- // --------------------------------------------------------
-
- private void ConvertMany(IEnumerable source, string outputFilename, Func convert)
- {
- IList primitives = source
- .Select(convert)
- .ToList();
-
- string content = JsonConvert.SerializeObject(primitives, Formatting.Indented);
-
- File.WriteAllText(outputFilename, content);
- }
-
- private ArmorPiecePrimitive ConvertArmorPiece(IArmorPiece armorPiece)
- {
- return new ArmorPiecePrimitive
+ var localizationQueries = new Dictionary>>();
+ Task> query = LoadBase(DB_KEY_DECORATIONS);
+ foreach (KeyValuePair localization in localizationKeys)
{
- Abilities = armorPiece.Abilities.Select(ConvertArmorAbility).ToList(),
- Assets = null,
- Attributes = new ArmorPieceAttributesPrimitive
- {
- RequiredGender = DataStructures.ArmorPieceAttributes.GenderToString(armorPiece.Attributes.RequiredGender)
- },
- Defense = ConvertDefense(armorPiece.Defense),
- Id = armorPiece.Id,
- Name = armorPiece.Name,
- Rarity = armorPiece.Rarity,
- Resistances = ConvertResistances(armorPiece.Resistances),
- Slots = ConvertSlots(armorPiece.Slots),
- Type = armorPiece.Type
- };
- }
+ localizationQueries[localization.Value] = LoadBase(localization.Key + "/" + DB_KEY_DECORATIONS);
+ }
- private ArmorAbilityPrimitive ConvertArmorAbility(IAbility ability)
- {
- return new ArmorAbilityPrimitive
- {
- SkillId = ability.Skill.Id,
- Level = ability.Level,
- };
- }
- private ArmorPieceDefensePrimitive ConvertDefense(IArmorPieceDefense defense)
- {
- return new ArmorPieceDefensePrimitive
- {
- Base = defense.Base,
- Max = defense.Max,
- Augmented = defense.Augmented
- };
- }
+ IList result = await query;
- private ArmorPieceResistancesPrimitive ConvertResistances(IArmorPieceResistances resistances)
- {
- return new ArmorPieceResistancesPrimitive
+ if (result == null)
{
- Fire = resistances.Fire,
- Water = resistances.Water,
- Thunder = resistances.Thunder,
- Ice = resistances.Ice,
- Dragon = resistances.Dragon
- };
- }
+ logger?.LogError("Query for all decorations failed");
+ return;
+ }
- private IList ConvertSlots(int[] slots)
- {
- return slots
- .Select(x => new ArmorSlotRank { Rank = x })
- .ToList();
- }
+ var localJewels = new Dictionary();
- private SkillPrimitive ConvertSkill(ISkill skill)
- {
- return new SkillPrimitive
+ foreach(JewelPrimitive jewelPrimitive in result)
{
- Id = skill.Id,
- Name = skill.Name,
- Description = skill.Description,
- Abilities = skill.Abilities.Select(ConvertSkillAbility).ToList()
- };
- }
+ IAbility[] localAbilities = jewelPrimitive.Abilitites.Select(a => GetAbility(a.SkillId, a.Level)).ToArray();
+ if (localJewels.ContainsKey(jewelPrimitive.Id))
+ logger?.LogError($"Decoration identifier with ID '{jewelPrimitive.Id}' and Name '{jewelPrimitive.Name}' is a duplicate");
+ else
+ localJewels[jewelPrimitive.Id] = new DataStructures.Jewel(jewelPrimitive, localAbilities);
+ }
- private AbilityPrimitive ConvertSkillAbility(IAbility ability)
- {
- return new AbilityPrimitive
+ foreach(KeyValuePair>> localizationQuery in localizationQueries)
{
- SkillId = ability.Skill.Id,
- Level = ability.Level,
- Description = ability.Description
- };
+ IList localization = await localizationQuery.Value;
+ if (localization == null)
+ {
+ logger?.LogError($"Query for decoration localization '{localizationQuery.Key}' failed");
+ continue;
+ }
+ foreach (JewelPrimitive jewel in localization)
+ {
+ localJewels[jewel.Id].AddLocalization(localizationQuery.Key, jewel);
+ }
+ }
+
+ jewels = localJewels.Values.ToArray();
}
- private CharmPrimitive ConvertCharm(ICharm charm)
+ private ICraftMaterial[] GetCraftingMaterials(CraftingCostPrimitive[] primitives)
{
- return new CharmPrimitive
+ var materials = new ICraftMaterial[primitives.Length];
+ for(int i = 0; i< primitives.Length;++i)
{
- Name = charm.Name,
- Levels = charm.Levels.Select(ConvertCharmLevel).ToList()
- };
+ materials[i] = new CraftMaterial(localizedItems.FirstOrDefault(x => x.Id == primitives[i].Item.Id), primitives[i].Quantity);
+ }
+ return materials;
}
- private CharmLevelPrimitive ConvertCharmLevel(ICharmLevel charmLevel)
+ private IAbility GetAbility(int skillId, int level)
{
- return new CharmLevelPrimitive
- {
- Name = charmLevel.Name,
- Level = charmLevel.Level,
- Rarity = charmLevel.Rarity,
- Abilitites = charmLevel.Abilities.Select(x => new AbilityIdPrimitive { SkillId = x.Skill.Id, Level = x.Level }).ToList()
- };
+ return abilities.FirstOrDefault(a => a.Skill.Id == skillId && a.Level == level);
}
- private JewelPrimitive ConvertJewel(IJewel jewel)
+ public Task GetArmorSetSkills()
{
- return new JewelPrimitive
- {
- Id = jewel.Id,
- Name = jewel.Name,
- Rarity = jewel.Rarity,
- SlotSize = jewel.SlotSize,
- Abilitites = jewel.Abilities.Select(x => new AbilityIdPrimitive { SkillId = x.Skill.Id, Level = x.Level }).ToList()
- };
+ throw new NotImplementedException();
}
}
}
diff --git a/MHArmory.MhwDbDataSource/DataStructures/Armor.cs b/MHArmory.MhwDbDataSource/DataStructures/Armor.cs
index 52d495e..d9d1481 100644
--- a/MHArmory.MhwDbDataSource/DataStructures/Armor.cs
+++ b/MHArmory.MhwDbDataSource/DataStructures/Armor.cs
@@ -25,12 +25,33 @@ internal class ArmorSetBonusPrimitive
public ArmorSetBonusRankPrimitive[] Ranks { get; set; }
}
+ internal class ArmorSetBonus : IArmorSetSkill
+ {
+ public int Id { get; private set; }
+
+ public Dictionary Name { get; private set; } = new Dictionary();
+
+ public IArmorSetSkillPart[] Parts { get; private set; }
+
+ internal ArmorSetBonus(ArmorSetBonusPrimitive primitive, IArmorSetSkillPart[] parts)
+ {
+ Id = primitive.Id;
+ Parts = parts;
+ AddLocalization("EN", primitive);
+ }
+ internal void AddLocalization(string languageKey, ArmorSetBonusPrimitive primitive)
+ {
+ if (primitive.Name != null)
+ Name[languageKey] = primitive.Name;
+ }
+ }
+
internal class ArmorPieceIdPrimitive
{
[JsonProperty("id")]
public int ArmorPieceId { get; set; }
}
-
+
internal class ArmorSetPrimitive
{
[JsonProperty("id")]
@@ -137,7 +158,7 @@ internal class ArmorPiecePrimitive
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("type")]
- public EquipmentType Type { get; set; }
+ public string Type { get; set; }
[JsonProperty("rarity")]
public int Rarity { get; set; }
[JsonProperty("defense")]
@@ -152,12 +173,15 @@ internal class ArmorPiecePrimitive
public IList Abilities { get; set; }
[JsonProperty("assets")]
public ArmorPieceAssets Assets { get; set; }
+ [JsonProperty("crafting")]
+ public CraftingPrimitive Crafting { get; set; }
+
}
internal class ArmorPiece : IArmorPiece
{
- public int Id { get; }
- public string Name { get; }
+ public int Id { get; set; }
+ public Dictionary Name { get; } = new Dictionary();
public EquipmentType Type { get; }
public int Rarity { get; }
public IArmorPieceDefense Defense { get; }
@@ -165,16 +189,17 @@ internal class ArmorPiece : IArmorPiece
public IArmorPieceAttributes Attributes { get; }
public int[] Slots { get; }
public IAbility[] Abilities { get; }
- public IArmorSetSkill[] ArmorSetSkills { get; }
+ public IArmorSetSkill[] ArmorSetSkills { get; private set; }
public IArmorPieceAssets Assets { get; }
- public IArmorSet ArmorSet { get; private set; }
+ public IFullArmorSet FullArmorSet { get; set; }
public IEvent Event { get; }
- public ArmorPiece(ArmorPiecePrimitive primitive, IAbility[] abilities)
+ public ICraftMaterial[] CraftMaterials { get; }
+
+ public ArmorPiece(ArmorPiecePrimitive primitive, IAbility[] abilities, ICraftMaterial[] materials)
{
Id = primitive.Id;
- Name = FixName(MapToGameName(primitive.Name));
- Type = primitive.Type;
+ Type = EquipmentTypeFromString(primitive.Type);
Rarity = primitive.Rarity;
Defense = primitive.Defense;
Resistances = primitive.Resistances;
@@ -182,12 +207,35 @@ public ArmorPiece(ArmorPiecePrimitive primitive, IAbility[] abilities)
Slots = primitive.Slots.Select(x => x.Rank).ToArray();
Abilities = abilities;
Assets = primitive.Assets;
- ArmorSet = null;
+ FullArmorSet = null;
+ CraftMaterials = materials;
+ AddLocalization("EN", primitive);
}
- internal void UpdateArmorSet(IArmorSet armorSet)
+ private EquipmentType EquipmentTypeFromString(string str)
{
- ArmorSet = armorSet;
+ if (str == "head")
+ return EquipmentType.Head;
+ if (str == "chest")
+ return EquipmentType.Chest;
+ if (str == "gloves")
+ return EquipmentType.Gloves;
+ if (str == "waist")
+ return EquipmentType.Waist;
+ if (str == "legs")
+ return EquipmentType.Legs;
+ throw new InvalidOperationException($"'{str}' is not a valid Equipmenttype");
+ }
+
+ internal void AddLocalization(string languageKey, ArmorPiecePrimitive primitive)
+ {
+ if (primitive.Name != null)
+ Name[languageKey] = MapToGameName(primitive.Name);
+ }
+
+ internal void UpdateArmorSetBoni(IArmorSetSkill[] boni)
+ {
+ ArmorSetSkills = boni;
}
private string MapToGameName(string name)
@@ -201,19 +249,18 @@ private string MapToGameName(string name)
if (name.EndsWith(" Gamma"))
return name.Substring(0, name.Length - 5) + "γ";
- return name;
- }
+ if (name.EndsWith(" Alpha +"))
+ return name.Substring(0, name.Length - 7) + "α+";
- private string FixName(string name)
- {
- if (name == "Death Stench Heels")
- return "Death Stench Heel";
- if (name == "Kulu-Yaku Head α")
- return "Kulu-Ya-Ku Head α";
+ if (name.EndsWith(" Beta +"))
+ return name.Substring(0, name.Length - 6) + "β+";
+
+ if (name.EndsWith(" Gamma +"))
+ return name.Substring(0, name.Length - 7) + "γ+";
return name;
}
-
+
public override string ToString()
{
return $"{Name} ({Abilities.Length} abilities)";
diff --git a/MHArmory.MhwDbDataSource/DataStructures/Charm.cs b/MHArmory.MhwDbDataSource/DataStructures/Charm.cs
index aabcf0f..3f0107d 100644
--- a/MHArmory.MhwDbDataSource/DataStructures/Charm.cs
+++ b/MHArmory.MhwDbDataSource/DataStructures/Charm.cs
@@ -25,10 +25,14 @@ internal class CharmLevelPrimitive
public int Rarity { get; set; }
[JsonProperty("skills")]
public IList Abilitites { get; set; }
+ [JsonProperty("crafting")]
+ public CraftingPrimitive Crafting { get; set; }
}
internal class CharmPrimitive
{
+ [JsonProperty("id")]
+ public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("ranks")]
@@ -40,20 +44,30 @@ internal class CharmLevel : ICharmLevel
public ICharm Charm { get; private set; }
public int Id { get; }
public EquipmentType Type { get; } = EquipmentType.Charm;
- public string Name { get; }
+ public Dictionary Name { get; private set; } = new Dictionary();
public int Level { get; }
public int Rarity { get; }
public int[] Slots { get; } = Array.Empty();
public IAbility[] Abilities { get; }
public IEvent Event { get; }
- internal CharmLevel(int id, CharmLevelPrimitive currentCharmLevelPrimitive, IAbility[] abilities)
+
+ public ICraftMaterial[] CraftMaterials { get; }
+
+ internal CharmLevel(int id, CharmLevelPrimitive currentCharmLevelPrimitive, IAbility[] abilities, ICraftMaterial[] materials)
{
Id = id;
- Name = currentCharmLevelPrimitive.Name;
Level = currentCharmLevelPrimitive.Level;
Rarity = currentCharmLevelPrimitive.Rarity;
Abilities = abilities;
+ CraftMaterials = materials;
+ AddLocalization("EN", currentCharmLevelPrimitive);
+ }
+
+ internal void AddLocalization(string languageKey, CharmLevelPrimitive primitive)
+ {
+ if (primitive.Name != null)
+ Name[languageKey] = primitive.Name;
}
public void UpdateCharm(ICharm charm)
@@ -66,4 +80,49 @@ public override string ToString()
return $"{Name}";
}
}
+
+ internal class Charm : ICharm
+ {
+ public int Id { get; private set; }
+
+ public Dictionary Name { get; private set; } = new Dictionary();
+
+ public ICharmLevel[] Levels { get; private set; }
+
+ internal Charm(CharmPrimitive primitive)
+ {
+ Id = primitive.Id;
+ Levels = null;
+ Name["EN"] = primitive.Name;
+ }
+
+ internal void SetCharmLevels(ICharmLevel[] levels)
+ {
+ Levels = levels;
+ }
+
+ internal void AddLocalization(string languageKey, CharmPrimitive primitive)
+ {
+ if (primitive.Name != null)
+ Name[languageKey] = primitive.Name;
+ if(primitive.Levels.Count != Levels.Length)
+ throw new InvalidOperationException($"Charm with ID '{primitive.Id}' in language '{languageKey}' has different number of levels ('{Levels.Length}' vs '{primitive.Levels.Count}')");
+
+ foreach (CharmLevelPrimitive charmLevelPrimitive in primitive.Levels)
+ {
+ bool found = false;
+ foreach(CharmLevel level in Levels)
+ {
+ if(level.Level == charmLevelPrimitive.Level)
+ {
+ found = true;
+ level.AddLocalization(languageKey, charmLevelPrimitive);
+ break;
+ }
+ }
+ if (!found)
+ throw new InvalidOperationException($"Charm with ID '{primitive.Id}' in language '{languageKey}' has charm level with level '{charmLevelPrimitive.Level}' which is not present in the original");
+ }
+ }
+ }
}
diff --git a/MHArmory.MhwDbDataSource/DataStructures/Item.cs b/MHArmory.MhwDbDataSource/DataStructures/Item.cs
new file mode 100644
index 0000000..3d10ce0
--- /dev/null
+++ b/MHArmory.MhwDbDataSource/DataStructures/Item.cs
@@ -0,0 +1,56 @@
+using MHArmory.Core.DataStructures;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MHArmory.MhwDbDataSource.DataStructures
+{
+ internal class CraftingCostItemPrimitive
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+ }
+ internal class CraftingCostPrimitive
+ {
+ [JsonProperty("quantity")]
+ public int Quantity { get; set; }
+ [JsonProperty("item")]
+ public CraftingCostItemPrimitive Item { get; set; }
+ }
+
+ internal class CraftingPrimitive
+ {
+ [JsonProperty("craftable")]
+ public bool Craftable { get; set; }
+ [JsonProperty("materials")]
+ public CraftingCostPrimitive[] Materials { get; set; }
+ }
+
+ internal class ItemPrimitive
+ {
+ [JsonProperty("id")]
+ public int Id { get; set; }
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ }
+
+ internal class LocalizedItem : ILocalizedItem
+ {
+ public int Id { get; }
+
+ public Dictionary Values { get; } = new Dictionary();
+
+ internal LocalizedItem(ItemPrimitive primitive)
+ {
+ Id = primitive.Id;
+ AddLocalization("EN", primitive);
+ }
+
+ internal void AddLocalization(string languageKey, ItemPrimitive primitive)
+ {
+ if (primitive.Name != null)
+ Values[languageKey] = primitive.Name;
+ }
+ }
+}
diff --git a/MHArmory.MhwDbDataSource/DataStructures/Jewel.cs b/MHArmory.MhwDbDataSource/DataStructures/Jewel.cs
index 549ff25..c9d190d 100644
--- a/MHArmory.MhwDbDataSource/DataStructures/Jewel.cs
+++ b/MHArmory.MhwDbDataSource/DataStructures/Jewel.cs
@@ -23,19 +23,26 @@ internal class JewelPrimitive
internal class Jewel : IJewel
{
- public int Id { get; }
- public string Name { get; }
- public int Rarity { get; }
- public int SlotSize { get; }
- public IAbility[] Abilities { get; }
+ public int Id { get; private set; }
+ public Dictionary Name { get; private set; } = new Dictionary();
+ public int Rarity { get; private set; }
+ public int SlotSize { get; private set; }
+ public IAbility[] Abilities { get; private set; }
+
internal Jewel(JewelPrimitive primitive, IAbility[] abilities)
{
Id = primitive.Id;
- Name = primitive.Name;
Rarity = primitive.Rarity;
SlotSize = primitive.SlotSize;
Abilities = abilities;
+ AddLocalization("EN", primitive);
+ }
+
+ internal void AddLocalization(string languageKey, JewelPrimitive primitive)
+ {
+ if(primitive.Name != null)
+ Name[languageKey] = primitive.Name;
}
public override string ToString()
diff --git a/MHArmory.MhwDbDataSource/DataStructures/Skill.cs b/MHArmory.MhwDbDataSource/DataStructures/Skill.cs
index 7c08dac..730e91f 100644
--- a/MHArmory.MhwDbDataSource/DataStructures/Skill.cs
+++ b/MHArmory.MhwDbDataSource/DataStructures/Skill.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -9,12 +9,12 @@ namespace MHArmory.MhwDbDataSource.DataStructures
{
internal class AbilityPrimitive
{
+ [JsonProperty("id")]
+ public int Id { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
- [JsonProperty("skillId")]
- public int SkillId { get; set; }
}
internal class SkillPrimitive
@@ -29,25 +29,84 @@ internal class SkillPrimitive
public IList Abilities { get; set; }
}
+ internal class Ability : IAbility
+ {
+ public int Id { get; private set; }
+
+ public ISkill Skill { get; private set; }
+
+ public int Level { get; private set; }
+
+ public Dictionary Description { get; private set; } = new Dictionary();
+
+ internal Ability(AbilityPrimitive primitive, ISkill skill)
+ {
+ Id = primitive.Id;
+ Level = primitive.Level;
+ Skill = skill;
+ }
+
+ internal void AddLocalization(string languageKey, AbilityPrimitive primitive)
+ {
+ if (primitive.Id != Id)
+ {
+ throw new InvalidOperationException($"Tried to add localization of ability primitive with ID '{primitive.Id}' to ability with ID '{Id}'");
+ }
+ if(primitive.Description != null)
+ Description[languageKey] = primitive.Description;
+ }
+ }
+
internal class Skill : ISkill
{
- public int Id { get; }
- public string Name { get; }
- public string Description { get; }
+ public int Id { get; private set; }
+ public Dictionary Name { get; private set; } = new Dictionary();
+ public Dictionary Description { get; private set; } = new Dictionary();
public int MaxLevel { get; private set; }
+ public string[] Categories { get; private set; }
public IAbility[] Abilities { get; private set; }
- internal Skill(int id, string name, string description)
+ internal Skill(SkillPrimitive primitive)
{
- Id = id;
- Name = name;
- Description = description;
+ Id = primitive.Id;
+ Abilities = new IAbility[primitive.Abilities.Count];
+ for (int i = 0; i < primitive.Abilities.Count; ++i)
+ Abilities[i] = new Ability(primitive.Abilities[i], this);
+ MaxLevel = Abilities.Max(x => x.Level);
+ AddLocalization("EN", primitive);
}
- internal void SetAbilities(IAbility[] abilities)
+ internal void AddLocalization(string languageKey, SkillPrimitive primitive)
{
- MaxLevel = abilities.Max(x => x.Level);
- Abilities = abilities;
+ if (primitive.Id != Id)
+ {
+ throw new InvalidOperationException($"Tried to add localization of skill primitive with ID '{primitive.Id}' to skill with ID '{Id}'");
+ }
+ if(primitive.Name != null)
+ Name[languageKey] = primitive.Name;
+ if(primitive.Description != null)
+ Description[languageKey] = primitive.Description;
+ if (Abilities.Length != primitive.Abilities.Count)
+ {
+ throw new InvalidOperationException($"Skill with ID '{primitive.Id}' in language '{languageKey}' has different number of abilities ('{Abilities.Length}' vs '{primitive.Abilities.Count}')");
+ }
+ foreach (AbilityPrimitive abilityPrimitive in primitive.Abilities)
+ {
+ bool found = false;
+ foreach (Ability ability in Abilities)
+ {
+ if (ability.Id == abilityPrimitive.Id)
+ {
+ found = true;
+ ability.AddLocalization(languageKey, abilityPrimitive);
+ break;
+ }
+ }
+ if (!found)
+ {
+ throw new InvalidOperationException($"Skill with ID '{primitive.Id}' in language '{languageKey}' has ability with ID '{abilityPrimitive.Id}' which is not present in the original");
+ }
+ }
}
public override string ToString()
diff --git a/MHArmory.MhwDbDataSource/MHArmory.MhwDbDataSource.csproj b/MHArmory.MhwDbDataSource/MHArmory.MhwDbDataSource.csproj
index ab607a0..10dba24 100644
--- a/MHArmory.MhwDbDataSource/MHArmory.MhwDbDataSource.csproj
+++ b/MHArmory.MhwDbDataSource/MHArmory.MhwDbDataSource.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/MHArmory.Search.Contracts/SolverDataJewelModel.cs b/MHArmory.Search.Contracts/SolverDataJewelModel.cs
index 17f9dbf..0f05baf 100644
--- a/MHArmory.Search.Contracts/SolverDataJewelModel.cs
+++ b/MHArmory.Search.Contracts/SolverDataJewelModel.cs
@@ -9,13 +9,16 @@ public class SolverDataJewelModel : IHasAbilities
{
public IJewel Jewel { get; }
public int Available { get; set; }
+ // Indicates if this is an automaticly generated jewel with multiskills where only on skill is wanted
+ public bool Generic { get; }
IAbility[] IHasAbilities.Abilities { get { return Jewel.Abilities; } }
- public SolverDataJewelModel(IJewel jewel, int available)
+ public SolverDataJewelModel(IJewel jewel, int available, bool generic = false)
{
Jewel = jewel;
Available = available;
+ Generic = generic;
}
}
}
diff --git a/MHArmory.Search/Default/Solver.cs b/MHArmory.Search/Default/Solver.cs
index a27764a..db52bd1 100644
--- a/MHArmory.Search/Default/Solver.cs
+++ b/MHArmory.Search/Default/Solver.cs
@@ -15,7 +15,7 @@ namespace MHArmory.Search.Default
public sealed class Solver : SolverBase, IDisposable
{
public override string Name { get; } = "Armory - Default";
- public override string Description { get; } = "Default search algorithm";
+ public override string Description { get; } = "Default search algorithm (only use for LR and HR)";
public override int Version { get; } = 1;
public void Dispose()
@@ -26,8 +26,9 @@ public void Dispose()
}
private readonly ObjectPool> jewelResultObjectPool = new ObjectPool>(() => new List());
- private readonly ObjectPool availableSlotsObjectPool = new ObjectPool(() => new int[3]);
- private readonly ObjectPool> armorSetSkillPartsObjectPool = new ObjectPool>(() => new Dictionary(ArmorSetSkillPartEqualityComparer.Default));
+ private readonly ObjectPool availableSlotsObjectPool = new ObjectPool(() => new int[4]);
+ private readonly ObjectPool> armorSetSkillPartsObjectPool =
+ new ObjectPool>(() => new Dictionary(SolverUtils.ArmorSetSkillPartEqualityComparer.Default));
private void ReturnSlotsArray(int[] slotsArray)
{
@@ -110,7 +111,7 @@ void OnArmorSetMismatch()
return ArmorSetSearchResult.NoMatch;
}
- if (ConsumeSlots(availableSlots, j.Jewel.SlotSize, requiredJewelCount) == false)
+ if (SolverUtils.ConsumeSlots(availableSlots, j.Jewel.SlotSize, requiredJewelCount) != requiredJewelCount)
{
OnArmorSetMismatch();
return ArmorSetSearchResult.NoMatch;
@@ -197,48 +198,5 @@ void Done()
Done();
return false;
}
-
- private static bool ConsumeSlots(int[] availableSlots, int jewelSize, int jewelCount)
- {
- for (int i = jewelSize - 1; i < availableSlots.Length; i++)
- {
- if (availableSlots[i] <= 0)
- continue;
-
- if (availableSlots[i] >= jewelCount)
- {
- availableSlots[i] -= jewelCount;
- return true;
- }
- else
- {
- jewelCount -= availableSlots[i];
- availableSlots[i] = 0;
- }
- }
-
- return jewelCount <= 0;
- }
-
- private class ArmorSetSkillPartEqualityComparer : IEqualityComparer
- {
- public static readonly IEqualityComparer Default = new ArmorSetSkillPartEqualityComparer();
-
- public bool Equals(IArmorSetSkillPart x, IArmorSetSkillPart y)
- {
- if (x == null || y == null)
- return false;
-
- return x.Id == y.Id;
- }
-
- public int GetHashCode(IArmorSetSkillPart obj)
- {
- if (obj == null)
- return 0;
-
- return obj.Id;
- }
- }
}
}
diff --git a/MHArmory.Search/Default/SolverData.cs b/MHArmory.Search/Default/SolverData.cs
index 47bd940..0818515 100644
--- a/MHArmory.Search/Default/SolverData.cs
+++ b/MHArmory.Search/Default/SolverData.cs
@@ -7,7 +7,7 @@
namespace MHArmory.Search.Default
{
- public class SolverData : ISolverData
+ public class SolverData : ISolverData, IConfigurable
{
public const double MinimumAverageSkillCompletionRatio = 0.75;
@@ -21,6 +21,8 @@ public class SolverData : ISolverData
public SolverDataJewelModel[] AllJewels { get; private set; }
public IAbility[] DesiredAbilities { get; private set; }
+ public bool IncludeLowerTier { get; set; }
+
private List inputHeads;
private List inputChests;
private List inputGloves;
@@ -29,12 +31,22 @@ public class SolverData : ISolverData
private List inputCharms;
private List inputJewels;
+ enum HunterRank { LowRank, HighRank, MasterRank };
+ private HunterRank hunterRank;
private int maxSkillCountPerArmorPiece;
+ private int maxSlotSize;
+ private int maxJewelId;
public string Name { get; } = "Armory - Default";
public string Author { get; } = "TanukiSharp";
public string Description { get; } = "Default solver data processor";
- public int Version { get; } = 1;
+ public int Version { get; } = 2;
+
+
+ public SolverData()
+ {
+ SolverDataViewModel.LoadSettings(this);
+ }
public void Setup(
IEquipment weapon,
@@ -61,9 +73,29 @@ IEnumerable desiredAbilities
maxSkills = Math.Max(maxSkills, inputGloves.MaxOrZero(x => x.Equipment.Abilities.Length));
maxSkills = Math.Max(maxSkills, inputWaists.MaxOrZero(x => x.Equipment.Abilities.Length));
maxSkills = Math.Max(maxSkills, inputLegs.MaxOrZero(x => x.Equipment.Abilities.Length));
-
maxSkillCountPerArmorPiece = maxSkills;
+ int maxRarity = inputHeads.MaxOrZero(x => x.Equipment.Rarity);
+ maxRarity = Math.Max(maxRarity, inputChests.MaxOrZero(x => x.Equipment.Rarity));
+ maxRarity = Math.Max(maxRarity, inputGloves.MaxOrZero(x => x.Equipment.Rarity));
+ maxRarity = Math.Max(maxRarity, inputWaists.MaxOrZero(x => x.Equipment.Rarity));
+ maxRarity = Math.Max(maxRarity, inputLegs.MaxOrZero(x => x.Equipment.Rarity));
+ if (maxRarity > 8)
+ hunterRank = HunterRank.MasterRank;
+ else if (maxRarity > 4)
+ hunterRank = HunterRank.HighRank;
+ else
+ hunterRank = HunterRank.LowRank;
+
+ int slotSize = inputHeads.MaxOrZero(x => x.Equipment.Slots.DefaultIfEmpty(0).Max());
+ slotSize = Math.Max(slotSize, inputChests.MaxOrZero(x => x.Equipment.Slots.DefaultIfEmpty(0).Max()));
+ slotSize = Math.Max(slotSize, inputGloves.MaxOrZero(x => x.Equipment.Slots.DefaultIfEmpty(0).Max()));
+ slotSize = Math.Max(slotSize, inputWaists.MaxOrZero(x => x.Equipment.Slots.DefaultIfEmpty(0).Max()));
+ slotSize = Math.Max(slotSize, inputLegs.MaxOrZero(x => x.Equipment.Slots.DefaultIfEmpty(0).Max()));
+ maxSlotSize = slotSize;
+
+ maxJewelId = inputJewels.Max(j => j.Jewel.Id);
+
Weapon = weapon;
DesiredAbilities = desiredAbilities.ToArray();
@@ -72,12 +104,17 @@ IEnumerable desiredAbilities
UpdateReferences();
}
+
private void ProcessInputData()
{
+ RemoveUnfittingJewels();
RemoveJewelsNotMatchingAnySkill();
+ CreateGenericJewels();
+
RemoveJewelsMatchingExcludedSkills();
RemoveEquipmentsBySkillExclusion();
+ RemoveEquipmentsByRarity();
ComputeMatchingSkillCount();
@@ -113,6 +150,44 @@ private void RemoveJewelsMatchingExcludedSkills()
inputJewels.RemoveAll(j => j.Jewel.Abilities.Any(a => IsMatchingExcludedSkill(a)));
}
+ private void RemoveUnfittingJewels()
+ {
+ inputJewels.RemoveAll(j => j.Jewel.SlotSize > maxSlotSize);
+ }
+
+ private void CreateGenericJewels()
+ {
+ if (hunterRank != HunterRank.MasterRank)
+ return;
+ bool isGenericJewel(SolverDataJewelModel jewel, IAbility ability)
+ {
+ return jewel.Jewel.Abilities.Length > 1 &&
+ jewel.Jewel.Abilities.Any(a => a.Skill == ability.Skill) &&
+ jewel.Jewel.Abilities.All(a => IsMatchingDesiredAbilities(a)) == false;
+ }
+ foreach (IAbility ability in DesiredAbilities)
+ {
+ IEnumerable genericJewels = inputJewels.Where(j => isGenericJewel(j, ability));
+ ++maxJewelId;
+ var name = new Dictionary()
+ {
+ { "EN", $"Any level 4 Deco with '{ability.Skill.Name["EN"]}' skill"}
+ };
+ var generic = new Jewel(maxJewelId, name, 0, 4, new IAbility[] { new Ability(ability.Skill, 1, ability.Description) });
+ int count = 0;
+ foreach(SolverDataJewelModel jewel in genericJewels)
+ {
+ if (int.MaxValue - jewel.Available < count)
+ count = int.MaxValue;
+ else
+ count += jewel.Available;
+
+ }
+ inputJewels.RemoveAll(j => isGenericJewel(j, ability));
+ inputJewels.Add(new SolverDataJewelModel(generic, count, true));
+ }
+ }
+
private bool IsMatchingDesiredAbilities(IAbility ability)
{
foreach (IAbility desiredAbility in DesiredAbilities)
@@ -152,6 +227,30 @@ private void RemoveEquipmentsBySkillExclusion(IList excludedAbilities,
equipments.RemoveAll(e => e.Equipment.Abilities.Any(a => excludedAbilities.Any(x => DataUtility.AreAbilitiesOnSameSkill(a, x))));
}
+ private void RemoveEquipmentsByRarity()
+ {
+ RemoveEquipmentsByRarity(inputHeads);
+ RemoveEquipmentsByRarity(inputChests);
+ RemoveEquipmentsByRarity(inputGloves);
+ RemoveEquipmentsByRarity(inputWaists);
+ RemoveEquipmentsByRarity(inputLegs);
+ }
+
+ private void RemoveEquipmentsByRarity(List equipments)
+ {
+ if (IncludeLowerTier)
+ return;
+ if (hunterRank == HunterRank.LowRank)
+ return;
+ int minimumRarity = 1;
+ if (hunterRank == HunterRank.HighRank)
+ minimumRarity = 5;
+ else
+ minimumRarity = 9;
+ equipments.RemoveAll(e => e.Equipment.Rarity < minimumRarity);
+ }
+
+
private void DeleteMarkedEquipments()
{
DeleteMarkedEquipments(inputHeads);
@@ -275,10 +374,22 @@ private void UpdateSelection(List equipments)
e.AverageSkillCompletionRatio >= MinimumAverageSkillCompletionRatio ||
e.MatchingSkillTotalLevel >= maxSkillCountPerArmorPiece ||
(e.MatchingSkillTotalLevel == e.Equipment.Abilities.Length && e.Equipment.Slots.Length > 0) ||
- (e.Equipment.Slots.Length >= 2 && e.Equipment.Slots.Count(x => x >= 2) >= 1);
+ HasRankAppropiateSlots(e.Equipment);
}
}
+ private bool HasRankAppropiateSlots(IEquipment equipment)
+ {
+ int sum = equipment.Slots.Sum();
+ if (hunterRank == HunterRank.MasterRank)
+ return sum > 6;
+ if (hunterRank == HunterRank.HighRank)
+ return sum > 3;
+ if (hunterRank == HunterRank.LowRank)
+ return sum > 0; // Does LR armor even have slots?
+ return false;
+ }
+
private void UnselectLessPoweredCharms()
{
foreach (IGrouping group in inputCharms.Where(x => x.Equipment is ICharmLevel).GroupBy(x => ((ICharmLevel)x.Equipment).Charm.Id))
@@ -351,6 +462,12 @@ List legs
}
}
}
+
+ public void Configure()
+ {
+ var window = new SolverDataConfigurationWindow(this);
+ window.ShowDialog();
+ }
}
public static class Operators
diff --git a/MHArmory.Search/Default/SolverDataConfigurationView.xaml b/MHArmory.Search/Default/SolverDataConfigurationView.xaml
new file mode 100644
index 0000000..1315716
--- /dev/null
+++ b/MHArmory.Search/Default/SolverDataConfigurationView.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml b/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml
new file mode 100644
index 0000000..b246020
--- /dev/null
+++ b/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml.cs b/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml.cs
new file mode 100644
index 0000000..4dacbc0
--- /dev/null
+++ b/MHArmory.Search/Default/SolverDataConfigurationWindow.xaml.cs
@@ -0,0 +1,29 @@
+using MHArmory.Search.Contracts;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace MHArmory.Search.Default
+{
+ public partial class SolverDataConfigurationWindow : Window
+ {
+ private readonly SolverDataViewModel viewModel;
+
+ public SolverDataConfigurationWindow(SolverData solver)
+ {
+ InitializeComponent();
+ viewModel = new SolverDataViewModel(solver);
+ DataContext = viewModel;
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ base.OnClosing(e);
+ viewModel.OnClose();
+ }
+ }
+}
diff --git a/MHArmory.Search/Default/SolverDataViewModel.cs b/MHArmory.Search/Default/SolverDataViewModel.cs
new file mode 100644
index 0000000..ff40f8c
--- /dev/null
+++ b/MHArmory.Search/Default/SolverDataViewModel.cs
@@ -0,0 +1,92 @@
+using MHArmory.Core.WPF;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Default
+{
+ internal class SolverDataViewModel : ViewModelBase
+ {
+ private class Configuration
+ {
+ [JsonProperty("IncludeLowerTier")]
+ public bool IncludeLowerTier { get; set; }
+ }
+
+
+ public int numericValue;
+ public int NumericValue
+ {
+ get { return numericValue; }
+ set
+ {
+ if (SetValue(ref numericValue, value))
+ Console.WriteLine($"{nameof(NumericValue)} changed to {numericValue}");
+ }
+ }
+
+ public bool IncludeLowerTier { get; set; } = false;
+
+ private SolverData solver;
+
+ public SolverDataViewModel(SolverData solver)
+ {
+ this.solver = solver;
+ LoadSettings();
+ }
+
+ private const string SettingsFilename = "solver-data-configuration.json";
+
+ private void LoadSettings()
+ {
+ string filename = Path.Combine(AppContext.BaseDirectory, SettingsFilename);
+
+ if (File.Exists(filename) == false)
+ return;
+
+ string jsonContent = File.ReadAllText(filename);
+
+ Configuration conf;
+
+ try
+ {
+ conf = JsonConvert.DeserializeObject(jsonContent);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error loading settings: {ex.Message}");
+ return;
+ }
+
+ IncludeLowerTier = conf.IncludeLowerTier;
+ }
+
+ private void SaveSettingsToSolver()
+ {
+ solver.IncludeLowerTier = IncludeLowerTier;
+ }
+
+ public void OnClose()
+ {
+ string jsonContent = JsonConvert.SerializeObject(new Configuration
+ {
+ IncludeLowerTier = IncludeLowerTier
+ }, Formatting.Indented);
+
+ File.WriteAllText(Path.Combine(AppContext.BaseDirectory, SettingsFilename), jsonContent);
+ SaveSettingsToSolver();
+ }
+
+ public static void LoadSettings(SolverData solver)
+ {
+ var model = new SolverDataViewModel(solver);
+ model.LoadSettings();
+ model.SaveSettingsToSolver();
+ }
+
+ }
+}
diff --git a/MHArmory.Search/Default/SolverUtils.cs b/MHArmory.Search/Default/SolverUtils.cs
index cc5c33b..115bb10 100644
--- a/MHArmory.Search/Default/SolverUtils.cs
+++ b/MHArmory.Search/Default/SolverUtils.cs
@@ -3,12 +3,34 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using MHArmory.Core;
using MHArmory.Core.DataStructures;
namespace MHArmory.Search.Default
{
public static class SolverUtils
{
+ public class ArmorSetSkillPartEqualityComparer : IEqualityComparer
+ {
+ public static readonly IEqualityComparer Default = new ArmorSetSkillPartEqualityComparer();
+
+ public bool Equals(IArmorSetSkillPart x, IArmorSetSkillPart y)
+ {
+ if (x == null || y == null)
+ return false;
+
+ return x.Id == y.Id;
+ }
+
+ public int GetHashCode(IArmorSetSkillPart obj)
+ {
+ if (obj == null)
+ return 0;
+
+ return obj.Id;
+ }
+ }
+
public static bool IsAnyFullArmorSet(IEquipment[] equipments)
{
foreach (IEquipment equipment in equipments)
@@ -52,5 +74,37 @@ public static int ComputeRequiredJewelsCount(int remaingAbilityLevels, int skill
return count;
}
+
+ public static int ConsumeSlots(int[] availableSlots, int jewelSize, int jewelCount, bool limitToExactSlotsize = false)
+ {
+ int slotted = 0;
+ for (int i = jewelSize - 1; i < availableSlots.Length; i++)
+ {
+ if (availableSlots[i] <= 0)
+ {
+ if (limitToExactSlotsize)
+ return slotted;
+ else
+ continue;
+ }
+
+ if (availableSlots[i] >= jewelCount)
+ {
+ availableSlots[i] -= jewelCount;
+ slotted += jewelCount;
+ return slotted;
+ }
+ else
+ {
+ jewelCount -= availableSlots[i];
+ slotted += availableSlots[i];
+ availableSlots[i] = 0;
+ }
+ if (limitToExactSlotsize)
+ return slotted;
+ }
+
+ return slotted;
+ }
}
}
diff --git a/MHArmory.Search/Iceborne/AbilityMap.cs b/MHArmory.Search/Iceborne/AbilityMap.cs
new file mode 100644
index 0000000..60a7da7
--- /dev/null
+++ b/MHArmory.Search/Iceborne/AbilityMap.cs
@@ -0,0 +1,55 @@
+using MHArmory.Core.DataStructures;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Iceborne
+{
+ class AbilityMap
+ {
+ private Dictionary abilities = new Dictionary();
+
+ public int this[ISkill skill]
+ {
+ get
+ {
+ if (abilities.TryGetValue(skill, out int value))
+ return value;
+ else
+ return 0;
+ }
+ }
+
+ public void AddAbility(IAbility a)
+ {
+ AddSkill(a.Skill, a.Level);
+ }
+
+ public void AddSkill(ISkill skill, int level)
+ {
+ if (abilities.ContainsKey(skill))
+ abilities[skill] += level;
+ else
+ abilities[skill] = level;
+ }
+
+ public void AddDecos(IJewel deco, int count = 1)
+ {
+ foreach (IAbility ability in deco.Abilities)
+ AddSkill(ability.Skill, ability.Level * count);
+ }
+
+ public void AddAbility(IAbility[] abilities)
+ {
+ foreach (IAbility a in abilities)
+ AddAbility(a);
+ }
+
+ public void Clear()
+ {
+ abilities.Clear();
+ }
+ }
+}
diff --git a/MHArmory.Search/Iceborne/ComparerSolverData.cs b/MHArmory.Search/Iceborne/ComparerSolverData.cs
new file mode 100644
index 0000000..072969c
--- /dev/null
+++ b/MHArmory.Search/Iceborne/ComparerSolverData.cs
@@ -0,0 +1,117 @@
+using MHArmory.Core.DataStructures;
+using MHArmory.Search.Contracts;
+using MHArmory.Search.Default;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Iceborne
+{
+ public class ComparerSolverData : ISolverData
+ {
+ public IEquipment Weapon { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllHeads { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllChests { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllGloves { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllWaists { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllLegs { get; private set; }
+
+ public ISolverDataEquipmentModel[] AllCharms { get; private set; }
+
+ public SolverDataJewelModel[] AllJewels { get; private set; }
+
+ public IAbility[] DesiredAbilities { get; private set; }
+
+ public string Name { get; } = "Comparer Solver Data";
+
+ public string Author { get; } = "ChaosSaber";
+
+ public string Description { get; } = "Solver Data which compares every equipment against each other and unselects every equipment which is worse then another";
+
+ public int Version { get; } = 1;
+
+
+
+ public bool IncludeLowerTier { get; set; }
+ public bool IfEqualUseArmorWithBetterSlots { get; set; }
+
+
+
+ private List inputHeads;
+ private List inputChests;
+ private List inputGloves;
+ private List inputWaists;
+ private List inputLegs;
+ private List inputCharms;
+ private List inputDecos;
+
+ //struct SkillDecoInfo
+ //{
+ // bool decoExists;
+ // int slotSize;
+ // bool hasLevel4Deco;
+ // int maxSkillPointsPerDeco;
+ // bool combiDecoWithOtherDesiredSkill;
+ //}
+ //private Dictionary decos = new Dictionary();
+
+
+ enum HunterRank { LowRank, HighRank, MasterRank };
+ //private HunterRank hunterRank;
+
+ public void Setup(IEquipment weapon, IEnumerable heads, IEnumerable chests, IEnumerable gloves, IEnumerable waists, IEnumerable legs, IEnumerable charms, IEnumerable jewels, IEnumerable desiredAbilities)
+ {
+ Weapon = weapon;
+ inputHeads = heads.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputChests = chests.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputGloves = gloves.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputWaists = waists.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputLegs = legs.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputCharms = charms.Select(x => new SolverDataEquipmentModel(x)).ToList();
+ inputDecos = jewels.ToList();
+ DesiredAbilities = desiredAbilities.ToArray();
+
+
+ //int maxRarity = inputHeads.MaxOrZero(x => x.Equipment.Rarity);
+ //maxRarity = Math.Max(maxRarity, inputChests.MaxOrZero(x => x.Equipment.Rarity));
+ //maxRarity = Math.Max(maxRarity, inputGloves.MaxOrZero(x => x.Equipment.Rarity));
+ //maxRarity = Math.Max(maxRarity, inputWaists.MaxOrZero(x => x.Equipment.Rarity));
+ //maxRarity = Math.Max(maxRarity, inputLegs.MaxOrZero(x => x.Equipment.Rarity));
+ //if (maxRarity > 8)
+ // hunterRank = HunterRank.MasterRank;
+ //else if (maxRarity > 4)
+ // hunterRank = HunterRank.HighRank;
+ //else
+ // hunterRank = HunterRank.LowRank;
+
+
+
+ CreateDecoMap();
+ FilterDecos();
+ UpdateDecoMap();
+
+ }
+
+ void CreateDecoMap()
+ {
+
+ }
+
+ void FilterDecos()
+ {
+
+ }
+
+ void UpdateDecoMap()
+ {
+
+ }
+ }
+}
diff --git a/MHArmory.Search/Iceborne/DecoCollection.cs b/MHArmory.Search/Iceborne/DecoCollection.cs
new file mode 100644
index 0000000..4e50044
--- /dev/null
+++ b/MHArmory.Search/Iceborne/DecoCollection.cs
@@ -0,0 +1,49 @@
+using MHArmory.Core.DataStructures;
+using MHArmory.Search.Contracts;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Iceborne
+{
+ class DecoCollection
+ {
+ public SolverDataJewelModel[] SingleSkillDecos { get; } // Decos with only 1 skill. Index +1 is the number points in this skill. A maximum of 4 points is possible
+ public SolverDataJewelModel GenericLevel4Deco { get; } // All Decos with additional skills which are not in the desired abilities list
+ public List MultiSkillDecos { get; } // All level 4 decos with another desired ability
+ public int TotalSkillLevels { get; }
+ public bool HasLevel4Deco { get; }
+ public ISkill Skill { get; }
+
+ public DecoCollection(IEnumerable allJewelsWithSkill, IAbility ability)
+ {
+ Skill = ability.Skill;
+ TotalSkillLevels = 0;
+ HasLevel4Deco = false;
+ SingleSkillDecos = new SolverDataJewelModel[4];
+ MultiSkillDecos = new List();
+ foreach (SolverDataJewelModel jewel in allJewelsWithSkill)
+ {
+ // we have to limit this calculation to not get overflows
+ TotalSkillLevels += Math.Min(jewel.Available, Skill.MaxLevel) * jewel.Jewel.Abilities.First(j => j.Skill == Skill).Level;
+ if (jewel.Jewel.SlotSize > 3 &&
+ (!jewel.Generic || jewel.Available > 0)) // we add a generic deco even if there are none
+ HasLevel4Deco = true;
+
+ if (jewel.Generic)
+ GenericLevel4Deco = jewel;
+ else if (jewel.Jewel.Abilities.Length > 1)
+ MultiSkillDecos.Add(jewel);
+ else
+ SingleSkillDecos[jewel.Jewel.Abilities[0].Level - 1] = jewel;
+ }
+ }
+
+ public void SortMultiSkillDecos(Dictionary skillCombinations)
+ {
+ MultiSkillDecos.OrderBy(d => skillCombinations[d.Jewel.Abilities.First(a => a.Skill != Skill).Skill]);
+ }
+ }
+}
diff --git a/MHArmory.Search/Iceborne/DecoMap.cs b/MHArmory.Search/Iceborne/DecoMap.cs
new file mode 100644
index 0000000..77a4436
--- /dev/null
+++ b/MHArmory.Search/Iceborne/DecoMap.cs
@@ -0,0 +1,44 @@
+using MHArmory.Core.DataStructures;
+using MHArmory.Search.Contracts;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Iceborne
+{
+ class DecoMap
+ {
+ Dictionary decos = new Dictionary();
+
+ public void AddDecos(IJewel deco, int count = 1)
+ {
+ if (decos.ContainsKey(deco))
+ decos[deco] += count;
+ else
+ decos[deco] = count;
+ }
+
+ public int this[IJewel deco]
+ {
+ get
+ {
+ if (decos.TryGetValue(deco, out int value))
+ return value;
+ else
+ return 0;
+ }
+ }
+
+ public void Clear()
+ {
+ decos.Clear();
+ }
+
+ public List ToArmorSetJewelResult()
+ {
+ return decos.Select(p => new ArmorSetJewelResult { Jewel = p.Key, Count = p.Value }).ToList();
+ }
+ }
+}
diff --git a/MHArmory.Search/Iceborne/IceborneSolver.cs b/MHArmory.Search/Iceborne/IceborneSolver.cs
new file mode 100644
index 0000000..1534f34
--- /dev/null
+++ b/MHArmory.Search/Iceborne/IceborneSolver.cs
@@ -0,0 +1,241 @@
+using MHArmory.Core;
+using MHArmory.Core.DataStructures;
+using MHArmory.Search.Contracts;
+using MHArmory.Search.Default;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MHArmory.Search.Iceborne
+{
+ public sealed class IceborneSolver : SolverBase, IDisposable
+ {
+ public override string Name { get; } = "Iceborne - Default";
+ public new string Author { get; } = "ChaosSaber";
+ public override string Description { get; } = "Default search algorithm for MR";
+ public override int Version { get; } = 1;
+
+ private Dictionary decos = new Dictionary();
+ private Dictionary desiredAbilities;
+ private List allDecos;
+
+ public void Dispose()
+ {
+ usedDecosObjectPool.Dispose();
+ availableSlotsObjectPool.Dispose();
+ abilitiesObjectPool.Dispose();
+ armorSetSkillPartsObjectPool.Dispose();
+ }
+
+ private readonly ObjectPool usedDecosObjectPool = new ObjectPool(() => new DecoMap());
+ private readonly ObjectPool availableSlotsObjectPool = new ObjectPool(() => new int[4]);
+ private readonly ObjectPool abilitiesObjectPool = new ObjectPool(() => new AbilityMap());
+ private readonly ObjectPool> armorSetSkillPartsObjectPool =
+ new ObjectPool>(() => new Dictionary(SolverUtils.ArmorSetSkillPartEqualityComparer.Default));
+
+ private void ReturnSlotsArray(int[] slotsArray)
+ {
+ for (int i = 0; i < slotsArray.Length; i++)
+ slotsArray[i] = 0;
+
+ availableSlotsObjectPool.PutObject(slotsArray);
+ }
+
+ private void GetArmorSkills(IEquipment[] equips, AbilityMap abilities, IEquipment weapon)
+ {
+ Dictionary armorSetSkillParts = armorSetSkillPartsObjectPool.GetObject();
+
+ void AddArmorSetSkills(IArmorSetSkill[] setSkills)
+ {
+
+ foreach (IArmorSetSkill armorSetSkill in setSkills)
+ {
+ foreach (IArmorSetSkillPart armorSetSkillPart in armorSetSkill.Parts)
+ {
+ if (armorSetSkillParts.ContainsKey(armorSetSkillPart))
+ ++armorSetSkillParts[armorSetSkillPart];
+ else
+ armorSetSkillParts[armorSetSkillPart] = 1;
+ }
+ }
+ }
+ foreach (IEquipment equipment in equips)
+ {
+ if (equipment == null)
+ continue;
+
+ foreach (IAbility a in equipment.Abilities)
+ abilities.AddAbility(a);
+
+ var armorPiece = equipment as IArmorPiece;
+ if (armorPiece == null)
+ continue;
+ if (armorPiece.ArmorSetSkills == null)
+ continue;
+ AddArmorSetSkills(armorPiece.ArmorSetSkills);
+ }
+
+ var weaponPiece = weapon as Weapon;
+ AddArmorSetSkills(weaponPiece.ArmorSetSkills);
+
+ foreach (KeyValuePair armorSetSkillPartKeyValue in armorSetSkillParts.Where(p => p.Value >= p.Key.RequiredArmorPieces))
+ {
+ foreach (IAbility x in armorSetSkillPartKeyValue.Key.GrantedSkills)
+ {
+ abilities.AddAbility(x);
+ }
+ }
+ armorSetSkillParts.Clear();
+ armorSetSkillPartsObjectPool.PutObject(armorSetSkillParts);
+ }
+
+ /**
+ * This function checks that the given armor set can fit all the desired skills.
+ * For this it will do the following:
+ * Gather all skills already present on the armor.
+ * Make a quick check if enough decos for every missing skillpoint is available.
+ * Try to fit the presorted deco list into the armour set.
+ * The first run Tries to not waste/improve skill points and a deco is limited to its own slotsize.
+ * The second run can waste/improve skill points and the deco is no longer limited to its own slotsize.
+ * Check if every desired Ability is satisfied.
+ */
+ protected override ArmorSetSearchResult IsArmorSetMatching(IEquipment weapon, IEquipment[] equips)
+ {
+ DecoMap usedDecos = usedDecosObjectPool.GetObject();
+ int[] availableSlots = availableSlotsObjectPool.GetObject();
+ AbilityMap abilities = abilitiesObjectPool.GetObject();
+
+ void Done()
+ {
+ usedDecos.Clear();
+ usedDecosObjectPool.PutObject(usedDecos);
+
+ abilities.Clear();
+ abilitiesObjectPool.PutObject(abilities);
+ }
+
+ void OnArmorSetMismatch()
+ {
+ Done();
+ ReturnSlotsArray(availableSlots);
+ }
+
+ int GetRemainingSkillLevels(ISkill skill)
+ {
+ return desiredAbilities[skill] - abilities[skill];
+ }
+
+ bool IsAbilityFullfilled(ISkill skill)
+ {
+ return GetRemainingSkillLevels(skill) <= 0;
+ }
+
+ SolverUtils.AccumulateAvailableSlots(weapon, availableSlots);
+ foreach (IEquipment equipment in equips)
+ SolverUtils.AccumulateAvailableSlots(equipment, availableSlots);
+
+ GetArmorSkills(equips, abilities, weapon);
+
+ // Quickckeck if we have enough decos
+ foreach (KeyValuePair skills in desiredAbilities)
+ {
+ int remaining = skills.Value - abilities[skills.Key];
+ if (remaining <= 0)
+ continue;
+ if (decos[skills.Key].TotalSkillLevels < remaining)
+ {
+ OnArmorSetMismatch();
+ return ArmorSetSearchResult.NoMatch;
+ }
+ }
+
+ /**
+ * returns the number of decos slotted
+ */
+ int AddDeco(IJewel deco, int count, bool limitToExactSlotsize = false)
+ {
+ int slotted = SolverUtils.ConsumeSlots(availableSlots, deco.SlotSize, count, limitToExactSlotsize);
+ if (slotted > 0)
+ {
+ usedDecos.AddDecos(deco, slotted);
+ abilities.AddDecos(deco, slotted);
+ }
+ return slotted;
+ }
+
+ foreach (SolverDataJewelModel solverData in allDecos)
+ {
+ int min = Math.Min(solverData.Jewel.Abilities.Min(a => GetRemainingSkillLevels(a.Skill) / a.Level), solverData.Available);
+ if (min <= 0)
+ continue;
+ AddDeco(solverData.Jewel, min, true);
+ }
+
+ foreach (SolverDataJewelModel solverData in allDecos)
+ {
+ int min = Math.Min(solverData.Jewel.Abilities.Max(a => (int)Math.Ceiling(1.0 * GetRemainingSkillLevels(a.Skill) / a.Level)), solverData.Available - usedDecos[solverData.Jewel]);
+ if (min <= 0)
+ continue;
+ AddDeco(solverData.Jewel, min, false);
+ }
+
+ foreach (KeyValuePair pair in desiredAbilities)
+ {
+ if (!IsAbilityFullfilled(pair.Key))
+ {
+ OnArmorSetMismatch();
+ return ArmorSetSearchResult.NoMatch;
+ }
+ }
+
+ // Sanity Check for testing
+ foreach (SolverDataJewelModel deco in allDecos)
+ {
+ if (usedDecos[deco.Jewel] > deco.Available)
+ {
+ throw new InvalidProgramException("Sanity Check failed: more decos used than available");
+ //OnArmorSetMismatch();
+ //return ArmorSetSearchResult.NoMatch;
+ }
+ }
+
+
+ var set = new ArmorSetSearchResult
+ {
+ IsMatch = true,
+ Jewels = usedDecos.ToArmorSetJewelResult(),
+ SpareSlots = availableSlots
+ };
+ Done();
+ return set;
+ }
+
+ protected override void OnSearchBegin(SolverDataJewelModel[] matchingJewels, IAbility[] desiredAbilities)
+ {
+ foreach (IAbility ability in desiredAbilities)
+ {
+ decos[ability.Skill] = new DecoCollection(matchingJewels.Where(j => j.Jewel.Abilities.Any(a => a.Skill == ability.Skill)), ability);
+ }
+
+ this.desiredAbilities = desiredAbilities.ToDictionary(x => x.Skill, x => x.Level);
+ var skillToSlotsize = decos.Values.ToDictionary(x => x.Skill, x => x.SingleSkillDecos[0] == null ? 4 : x.SingleSkillDecos[0].Jewel.SlotSize);
+
+ allDecos = matchingJewels
+ .Where(x => x.Available > 0)
+ .OrderByDescending(x => x.Jewel.Abilities.Sum(a => a.Level))
+ .ThenByDescending(x => x.Jewel.Abilities.Sum(a => skillToSlotsize[a.Skill] * a.Level))
+ .ThenByDescending(x => x.Jewel.SlotSize)
+ .ToList();
+
+ var combinations = decos.Values
+ .Where(x => x.MultiSkillDecos.Count > 0)
+ .ToDictionary(x => x.Skill, x => x.MultiSkillDecos.Count);
+
+ foreach (KeyValuePair decoPair in decos)
+ decoPair.Value.SortMultiSkillDecos(combinations);
+ }
+ }
+}
diff --git a/MHArmory.Search/MHArmory.Search.csproj b/MHArmory.Search/MHArmory.Search.csproj
index 9e34e34..9841a5a 100644
--- a/MHArmory.Search/MHArmory.Search.csproj
+++ b/MHArmory.Search/MHArmory.Search.csproj
@@ -12,6 +12,11 @@
+
+
+
+
+
diff --git a/MHArmory.SearchTests/Iceborne/ComparerSolverDataTests.cs b/MHArmory.SearchTests/Iceborne/ComparerSolverDataTests.cs
new file mode 100644
index 0000000..6b513e6
--- /dev/null
+++ b/MHArmory.SearchTests/Iceborne/ComparerSolverDataTests.cs
@@ -0,0 +1,1221 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MHArmory.Search.Iceborne;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MHArmory.Core.DataStructures;
+using MHArmory.Search.Contracts;
+
+namespace MHArmory.Search.Iceborne.Tests
+{
+ [TestClass()]
+ public class ComparerSolverDataTests
+ {
+ private static IEquipment CreateWeapon()
+ {
+ return new Weapon(0, WeaponType.Bow, Array.Empty(), Array.Empty(), null);
+ }
+
+ private static IArmorPiece CreateArmor(int id, EquipmentType type, int rarity, int[] slots, IAbility[] abilities, IArmorSetSkill[] setSkills)
+ {
+ return new ArmorPiece(id, new Dictionary(), type, rarity, slots,
+ abilities, setSkills, new ArmorPieceDefense(0, 0, 0),
+ new ArmorPieceResistances(0, 0, 0, 0, 0), new ArmorPieceAttributes(Gender.Both),
+ null, null, null, null);
+ }
+
+ private static ISkill CreateSkill(int id)
+ {
+ return new Skill(id, new Dictionary(), new Dictionary(), null, null);
+ }
+
+ private static IAbility CreateAbility(ISkill skill, int level)
+ {
+ return new Ability(skill, level, new Dictionary());
+ }
+
+ private static ICharmLevel CreateCharm(int id, IAbility[] abilities)
+ {
+ return new CharmLevel(id, 1, new Dictionary(), 12, Array.Empty(), abilities, null, null);
+ }
+
+ private static SolverDataJewelModel CreateDeco(int id, int slotSize, IAbility[] abilities, int available, bool generic = false)
+ {
+ return new SolverDataJewelModel(new Jewel(id, new Dictionary(), 12, slotSize, abilities), available, generic);
+ }
+
+ private static IArmorSetSkill CreateArmorSetSkill(int id, int skill)
+ {
+ return new ArmorSetSkill(id, new Dictionary(), new IArmorSetSkillPart[] { new ArmorSetSkillPart(id, 1, new IAbility[] { CreateAbility(CreateSkill(skill), 1) }) });
+ }
+
+ [TestMethod()]
+ public void SetupSortOutExcludedSkillsTest()
+ {
+ var comparer = new ComparerSolverData();
+
+ IEnumerable CreateArmors(EquipmentType type)
+ {
+ return new List()
+ {
+ CreateArmor(0, type, 12, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(0), 1)},null),
+ CreateArmor(1, type, 12, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(1), 1)},null)
+ };
+ }
+
+ IEnumerable heads = CreateArmors(EquipmentType.Head);
+ IEnumerable chests = CreateArmors(EquipmentType.Chest);
+ IEnumerable arms = CreateArmors(EquipmentType.Gloves);
+ IEnumerable waist = CreateArmors(EquipmentType.Waist);
+ IEnumerable legs = CreateArmors(EquipmentType.Legs);
+ var charms = new List()
+ {
+ CreateCharm(0, new IAbility[] { CreateAbility(CreateSkill(0), 1)}),
+ CreateCharm(1, new IAbility[] { CreateAbility(CreateSkill(1), 1)})
+ };
+
+ comparer.Setup(CreateWeapon(), heads, chests, arms, waist, legs, charms, new List(), new IAbility[] { CreateAbility(CreateSkill(0), 0) });
+
+ Assert.Equals(1, comparer.AllHeads.Count());
+ Assert.Equals(1, comparer.AllHeads[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllHeads[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllChests.Count());
+ Assert.Equals(1, comparer.AllChests[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllChests[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllGloves.Count());
+ Assert.Equals(1, comparer.AllGloves[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllGloves[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllWaists.Count());
+ Assert.Equals(1, comparer.AllWaists[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllWaists[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllLegs.Count());
+ Assert.Equals(1, comparer.AllLegs[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllLegs[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllCharms.Count());
+ Assert.Equals(1, comparer.AllCharms[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllCharms[0].IsSelected);
+ }
+
+ [TestMethod()]
+ public void SetupInlcudeLowerTierTest()
+ {
+ var comparer = new ComparerSolverData();
+ comparer.IncludeLowerTier = true;
+
+ IEnumerable CreateArmors(EquipmentType type)
+ {
+ return new List()
+ {
+ CreateArmor(0, type, 8, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(0), 1)},null),
+ CreateArmor(1, type, 12, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(1), 1)},null)
+ };
+ }
+
+ IEnumerable heads = CreateArmors(EquipmentType.Head);
+ IEnumerable chests = CreateArmors(EquipmentType.Chest);
+ IEnumerable arms = CreateArmors(EquipmentType.Gloves);
+ IEnumerable waist = CreateArmors(EquipmentType.Waist);
+ IEnumerable legs = CreateArmors(EquipmentType.Legs);
+ var charms = new List()
+ {
+ CreateCharm(0, new IAbility[] { CreateAbility(CreateSkill(0), 1)}),
+ CreateCharm(1, new IAbility[] { CreateAbility(CreateSkill(1), 1)})
+ };
+
+ comparer.Setup(CreateWeapon(), heads, chests, arms, waist, legs, charms, new List(), new IAbility[] { });
+
+ Assert.Equals(2, comparer.AllHeads.Count());
+ Assert.Equals(2, comparer.AllChests.Count());
+ Assert.Equals(2, comparer.AllGloves.Count());
+ Assert.Equals(2, comparer.AllWaists.Count());
+ Assert.Equals(2, comparer.AllLegs.Count());
+ Assert.Equals(2, comparer.AllCharms.Count());
+ }
+
+
+ [TestMethod()]
+ public void SetupDontInlcudeLowerTierTest()
+ {
+ var comparer = new ComparerSolverData();
+ comparer.IncludeLowerTier = false;
+
+ IEnumerable CreateArmors(EquipmentType type)
+ {
+ return new List()
+ {
+ CreateArmor(0, type, 8, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(0), 1)},null),
+ CreateArmor(1, type, 12, new int[] { }, new IAbility[] {CreateAbility(CreateSkill(1), 1)},null)
+ };
+ }
+
+ IEnumerable heads = CreateArmors(EquipmentType.Head);
+ IEnumerable chests = CreateArmors(EquipmentType.Chest);
+ IEnumerable arms = CreateArmors(EquipmentType.Gloves);
+ IEnumerable waist = CreateArmors(EquipmentType.Waist);
+ IEnumerable legs = CreateArmors(EquipmentType.Legs);
+ var charms = new List()
+ {
+ CreateCharm(0, new IAbility[] { CreateAbility(CreateSkill(0), 1)}),
+ CreateCharm(1, new IAbility[] { CreateAbility(CreateSkill(1), 1)})
+ };
+
+ comparer.Setup(CreateWeapon(), heads, chests, arms, waist, legs, charms, new List(), new IAbility[] { });
+
+ Assert.Equals(1, comparer.AllHeads.Count());
+ Assert.Equals(1, comparer.AllHeads[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllHeads[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllChests.Count());
+ Assert.Equals(1, comparer.AllChests[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllChests[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllGloves.Count());
+ Assert.Equals(1, comparer.AllGloves[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllGloves[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllWaists.Count());
+ Assert.Equals(1, comparer.AllWaists[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllWaists[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllLegs.Count());
+ Assert.Equals(1, comparer.AllLegs[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllLegs[0].IsSelected);
+
+ Assert.Equals(1, comparer.AllCharms.Count());
+ Assert.Equals(1, comparer.AllCharms[0].Equipment.Id);
+ Assert.IsTrue(comparer.AllCharms[0].IsSelected);
+ }
+
+
+ [TestMethod()]
+ public void SetupUncomparable_differentSKills_NoSlots_Test()
+ {
+ var comparer = new ComparerSolverData();
+
+ IEnumerable