diff --git a/ScoutHelper/Config/Configuration.cs b/ScoutHelper/Config/Configuration.cs index b33747e..07f4c39 100644 --- a/ScoutHelper/Config/Configuration.cs +++ b/ScoutHelper/Config/Configuration.cs @@ -5,6 +5,7 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using ScoutHelper.Models; +using XIVHuntUtils.Models; using static ScoutHelper.Utils.Utils; namespace ScoutHelper.Config; diff --git a/ScoutHelper/Constants.cs b/ScoutHelper/Constants.cs index c81c6d3..43276a0 100644 --- a/ScoutHelper/Constants.cs +++ b/ScoutHelper/Constants.cs @@ -3,6 +3,7 @@ using System.Net.Http.Headers; using System.Reflection; using ScoutHelper.Models; +using XIVHuntUtils.Models; namespace ScoutHelper; diff --git a/ScoutHelper/Managers/BearManager.cs b/ScoutHelper/Managers/BearManager.cs index e774263..a8c4b8c 100644 --- a/ScoutHelper/Managers/BearManager.cs +++ b/ScoutHelper/Managers/BearManager.cs @@ -12,6 +12,8 @@ using ScoutHelper.Models; using ScoutHelper.Models.Http; using ScoutHelper.Utils; +using XIVHuntUtils.Models; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Managers; diff --git a/ScoutHelper/Managers/InitializationManager.cs b/ScoutHelper/Managers/InitializationManager.cs index 96f00b4..ed9f8fe 100644 --- a/ScoutHelper/Managers/InitializationManager.cs +++ b/ScoutHelper/Managers/InitializationManager.cs @@ -1,7 +1,8 @@ using System.Linq; using Dalamud.Plugin.Services; using ScoutHelper.Config; -using ScoutHelper.Models; +using XIVHuntUtils.Managers; +using XIVHuntUtils.Models; using static ScoutHelper.Utils.Utils; namespace ScoutHelper.Managers; @@ -50,5 +51,5 @@ private void InitializeInstanceMap() { } private void InitializeTerritoryInstances() => - TerritoryExtensions.SetTerritoryInstances(_conf, _territoryManager.GetTerritoryIds()); + TerritoryExtensions.SetTerritoryInstances(_conf.Instances, _territoryManager.GetTerritoryIds()); } diff --git a/ScoutHelper/Managers/MobManager.cs b/ScoutHelper/Managers/MobManager.cs deleted file mode 100644 index 81fd0f8..0000000 --- a/ScoutHelper/Managers/MobManager.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CSharpFunctionalExtensions; -using Dalamud.Game; -using Dalamud.Plugin.Services; -using Lumina.Excel.GeneratedSheets2; -using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; - -namespace ScoutHelper.Managers; - -public class MobManager { - private readonly IPluginLog _log; - - private readonly IDictionary _mobNameToId; - private readonly IDictionary _mobIdToName; - - public MobManager(IPluginLog log, IDataManager dataManager) { - _log = log; - - (_mobNameToId, _mobIdToName) = LoadData(dataManager); - } - - public Maybe GetMobId(string mobName) => _mobNameToId.MaybeGet(mobName.Lower()); - - public Maybe GetMobName(uint mobId) => _mobIdToName.MaybeGet(mobId); - - private (IDictionary nameToId, IDictionary idToName) LoadData( - IDataManager dataManager - ) { - _log.Debug("Building mob data from game files..."); - - var notoriousMonsters = dataManager.GetExcelSheet(ClientLanguage.English)! - .Select(monster => monster.BNpcName.Row) - .ToImmutableHashSet(); - - var nameToId = dataManager.GetExcelSheet(ClientLanguage.English)! - .Select(name => (name: name.Singular.RawString.Lower(), mobId: name.RowId)) - .Where(name => notoriousMonsters.Contains(name.mobId)) - .GroupBy(entry => entry.name) - .Select( - grouping => { - if (1 < grouping.Count()) { - _log.Debug( - "Duplicate mobs found for name [{0:l}]: {1:l}", - grouping.Key, - grouping.Select(entry => entry.mobId.ToString()).Join(", ") - ); - } - return grouping.First(); - } - ) - .ToDict(); - var idToName = nameToId.Flip(); - - _log.Debug("Mob data built."); - - return (nameToId, idToName); - } -} diff --git a/ScoutHelper/Managers/SirenManager.cs b/ScoutHelper/Managers/SirenManager.cs index 6f57dc1..208d3e1 100644 --- a/ScoutHelper/Managers/SirenManager.cs +++ b/ScoutHelper/Managers/SirenManager.cs @@ -9,14 +9,16 @@ using CSharpFunctionalExtensions; using Dalamud.Plugin.Services; using Dalamud.Utility; +using DitzyExtensions.Functional; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using ScoutHelper.Config; using ScoutHelper.Models; using ScoutHelper.Models.Json; using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; +using XIVHuntUtils.Managers; +using XIVHuntUtils.Models; using static ScoutHelper.Utils.Utils; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Managers; @@ -40,7 +42,9 @@ MobManager mobManager (_patchData, _mobToPatch) = LoadData(options.SirenDataFile, territoryManager, mobManager); } - public AccResults, string> GenerateSirenLink(IList mobList) { + public AccumulatedResults, string> GenerateSirenLink( + IList mobList + ) { _log.Debug("Generating a siren link for mob list: {0}", mobList); var patches = mobList @@ -53,9 +57,9 @@ MobManager mobManager _log.Debug("Patches represented in mob list: {0}", patches); if (patches.IsEmpty()) - return AccResults.From( + return AccumulatedResults.From( Maybe<(string, Patch)>.None, - "No mobs in the train are supported by Siren Hunts ;-;".AsSingletonList() + "No mobs in the train are supported by Siren Hunts ;-;" ); return patches @@ -140,7 +144,7 @@ MobManager mobManager return (patchesData.Value, mobToPatch); } - private static AccResults<(Patch patch, SirenPatchData), string> ParsePatchData( + private static AccumulatedResults<(Patch patch, SirenPatchData), string> ParsePatchData( TerritoryManager territoryManager, MobManager mobManager, KeyValuePair patchData @@ -159,11 +163,7 @@ KeyValuePair patchData .Map( mapId => mapMobs .Mobs - .SelectResults( - mobName => mobManager - .GetMobId(mobName) - .ToResult($"No mobId found for mobName: {mobName}") - ) + .SelectResults(mobManager.GetMobId) .WithValue(mobIds => SirenMapData.From(mapId, mobIds)) ) ); @@ -200,7 +200,7 @@ KeyValuePair patchData ) .WithValue(value => value.ToDict()); - return parsedMobOrder.Join( + return parsedMobOrder.JoinWith( mapResults, (mobOrder, maps) => (patch, SirenPatchData.From(mobOrder, maps)) ); diff --git a/ScoutHelper/Managers/TerritoryManager.cs b/ScoutHelper/Managers/TerritoryManager.cs deleted file mode 100644 index 674bb45..0000000 --- a/ScoutHelper/Managers/TerritoryManager.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using CSharpFunctionalExtensions; -using Dalamud.Game; -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using Lumina.Excel.GeneratedSheets2; -using ScoutHelper.Models; -using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; -using static ScoutHelper.Utils.Utils; - -namespace ScoutHelper.Managers; - -public class TerritoryManager { - private readonly IDalamudPluginInterface _pluginInterface; - private readonly IPluginLog _log; - - private readonly IDictionary _nameToId; - private readonly IDictionary> _idToName; - - public TerritoryManager(IDalamudPluginInterface pluginInterface, IPluginLog log, IDataManager dataManager) { - _pluginInterface = pluginInterface; - _log = log; - - (_nameToId, _idToName) = LoadData(dataManager); - } - - public Maybe FindTerritoryId(string territoryName) => _nameToId.MaybeGet(territoryName.Lower()); - - public Result GetTerritoryId(string territoryName) => - FindTerritoryId(territoryName) - .ToResult($"Failed to find a territoryId for map name: {territoryName}"); - - public Maybe GetTerritoryName(uint territoryId) => - _idToName - .MaybeGet(_pluginInterface.UiLanguage) - .Bind(nameMap => nameMap.MaybeGet(territoryId)); - - public IEnumerable<(Territory territory, uint territoryId)> GetTerritoryIds() => - GetEnumValues() - .SelectResults( - territory => GetTerritoryId(territory.Name()) - .Map(territoryId => (territory, territoryId)) - ) - .ForEachError(error => _log.Debug(error)) - .Value; - - public IEnumerable<(uint territoryId, uint instances)> GetDefaultInstancesForIds() => - GetTerritoryIds() - .Select( - territory => (territory.territoryId, territory.territory.DefaultInstances()) - ); - - private (IDictionary nameToId, IDictionary> idToName) LoadData( - IDataManager dataManager - ) { - _log.Debug("Building map data from game files..."); - - var supportedMapNames = GetEnumValues() - .SelectMany(patch => patch.HuntMaps()) - .Select(map => map.Name()) - .ToImmutableHashSet(); - - var supportedPlaceIds = dataManager.GetExcelSheet(ClientLanguage.English)! - .Where(place => supportedMapNames.Contains(place.Name.RawString.Lower())) - .ForEach(place => _log.Verbose("Found PlaceName: {0} | {1:l}", place.RowId, place.Name)) - .Select(place => place.RowId) - .ToImmutableHashSet(); - - var dataDicts = GetEnumValues() - .Select( - language => { - var placeNames = dataManager - .GetExcelSheet(language)! - .Where(name => supportedPlaceIds.Contains(name.RowId)) - .Select(name => (name.RowId, name.Name.RawString)) - .ToDict(); - - var idToName = dataManager - .GetExcelSheet(language)! - .Where(territory => territory.TerritoryIntendedUse.Row == 1) - .Where(territory => placeNames.ContainsKey(territory.PlaceName.Row)) - .Select(territory => (mapId: territory.RowId, name: placeNames[territory.PlaceName.Row])) - .GroupBy(map => map.name) - .Select( - grouping => { - if (1 < grouping.Count()) { - _log.Debug( - "[{2:l}] Duplicate maps found for name [{0:l}]: {1:l}", - grouping.Key, - grouping.Select(place => place.mapId.ToString()).Join(", "), - language.GetLanguageCode() - ); - } - return grouping.First(); - } - ) - .ForEach( - territory => _log.Verbose( - "[{2:l}] Found territoryId [{0}] for place: {1:l}", - territory.mapId, - territory.name, - language.GetLanguageCode() - ) - ) - .ToDict(); - - var nameToId = idToName - .Flip() - .Select(entry => (entry.Key.Lower(), entry.Value)) - .ToDict(); - - return (nameToId, (language.GetLanguageCode(), idToName)); - } - ) - .Unzip( - (ts, us) => ( - ts - .SelectMany(nameToId => nameToId.AsPairs()) - .ToDict(), - us - .ToDict() - ) - ); - - _log.Debug("Map data built."); - - return dataDicts; - } -} - -internal static class TerritoryManagerExtensions { - private static IDictionary _langCodes = new Dictionary() { - { ClientLanguage.Japanese, "jp" }, - { ClientLanguage.English, "en" }, - { ClientLanguage.German, "de" }, - { ClientLanguage.French, "fr" }, - }.VerifyEnumDictionary(); - - public static string GetLanguageCode(this ClientLanguage language) => _langCodes[language]; -} diff --git a/ScoutHelper/Managers/TurtleManager.cs b/ScoutHelper/Managers/TurtleManager.cs index 60e6477..dbe5b8f 100644 --- a/ScoutHelper/Managers/TurtleManager.cs +++ b/ScoutHelper/Managers/TurtleManager.cs @@ -7,15 +7,18 @@ using CSharpFunctionalExtensions; using Dalamud.Plugin.Services; using Dalamud.Utility; +using DitzyExtensions.Functional; using Newtonsoft.Json; using ScoutHelper.Config; using ScoutHelper.Models; using ScoutHelper.Models.Http; using ScoutHelper.Models.Json; using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; +using XIVHuntUtils.Managers; +using XIVHuntUtils.Models; using static ScoutHelper.Managers.TurtleHttpStatus; using static ScoutHelper.Utils.Utils; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Managers; @@ -104,7 +107,10 @@ public async Task UpdateCurrentSession(IList train) mob.Position) ) ), - ( content) => _httpClientGenerator.Client.PatchAsync($"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", content) + (content) => _httpClientGenerator.Client.PatchAsync( + $"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", + content + ) ).TapError( error => { if (error.ErrorType == HttpErrorType.Timeout) { @@ -140,7 +146,7 @@ public async Task> GenerateTurtleLink( return await HttpUtils.DoRequest( _log, TurtleTrainRequest.CreateRequest(spawnPoints), - ( content) => _httpClientGenerator.Client.PostAsync(_conf.TurtleApiTrainPath, content), + (content) => _httpClientGenerator.Client.PostAsync(_conf.TurtleApiTrainPath, content), trainResponse => TurtleLinkData.From(trainResponse, highestPatch) ) .HandleHttpError( @@ -155,12 +161,17 @@ public async Task> GenerateTurtleLink( private Maybe<(uint mapId, uint instance, uint pointId, uint mobId)> GetRequestInfoForMob(TrainMob mob) => TerritoryIdToTurtleData .MaybeGet(mob.TerritoryId) - .Select(mapData => mapData.TurtleId) - .Join(mob.Instance.AsTurtleInstance()) - .Join(GetNearestSpawnPoint(mob)) - .Select(tuple => tuple.Flatten()) - .Join(MobIdToTurtleId[mob.MobId].turtleMobId) - .Select(tuple => tuple.Flatten()); + .SelectMany( + mapData => GetNearestSpawnPoint(mob) + .Select( + nearestSpawnPoint => ( + mapData.TurtleId, + mob.Instance.AsTurtleInstance(), + nearestSpawnPoint, + MobIdToTurtleId[mob.MobId].turtleMobId + ) + ) + ); private Maybe GetNearestSpawnPoint(TrainMob mob) => TerritoryIdToTurtleData @@ -207,7 +218,7 @@ MobManager mobManager } private static - AccResults<(MobDict, TerritoryDict), string> ParsePatchData( + AccumulatedResults<(MobDict, TerritoryDict), string> ParsePatchData( TerritoryManager territoryManager, MobManager mobManager, KeyValuePair patchData @@ -222,7 +233,6 @@ KeyValuePair patchData .SelectResults( patchMob => mobManager .GetMobId(patchMob.Key) - .ToResult($"No mobId found for mobName: {patchMob.Key}") .Map(mobId => (mobId, (patch, patchMob.Value))) ) .WithValue(mobs => mobs.ToDict()); @@ -251,7 +261,7 @@ KeyValuePair patchData ) .WithValue(territoriesAsPairs => territoriesAsPairs.ToDict()); - return parsedMobs.Join(parsedTerritories, (mobs, territories) => (mobs, territories)); + return parsedMobs.JoinWith(parsedTerritories, (mobs, territories) => (mobs, territories)); } } diff --git a/ScoutHelper/Models/Patch.cs b/ScoutHelper/Models/Patch.cs deleted file mode 100644 index 20b7a3d..0000000 --- a/ScoutHelper/Models/Patch.cs +++ /dev/null @@ -1,64 +0,0 @@ -// ReSharper disable InconsistentNaming - -using System; -using System.Collections.Generic; -using System.Linq; -using static ScoutHelper.Models.Territory; - -namespace ScoutHelper.Models; - -public enum Patch { - ARR, - HW, - SB, - SHB, - EW, - DT, -} - -public static class PatchExtensions { - private static readonly IDictionary> PatchHuntMaps = new (Patch, IList)[] { - (Patch.ARR, Array.Empty()), // TODO: add arr maps - (Patch.HW, new[] { - CoerthasWesternHighlands, TheSeaOfClouds, AzysLla, - TheDravanianForelands, TheDravanianHinterlands, TheChurningMists, - }), - (Patch.SB, new[] { - TheFringes, ThePeaks, TheLochs, - TheRubySea, Yanxia, TheAzimSteppe, - }), - (Patch.SHB, new[] { - Lakeland, Kholusia, AmhAraeng, - IlMheg, TheRaktikaGreatwood, TheTempest, - }), - (Patch.EW, new[] { - Labyrinthos, Thavnair, Garlemald, - MareLamentorum, Elpis, UltimaThule, - }), - (Patch.DT, new[] { - Urqopacha, Kozamauka, YakTel, - Shaaloani, HeritageFound, LivingMemory, - }), - } - .Select(patch => (patch.Item1, patch.Item2.AsList())) - .ToDict() - .VerifyEnumDictionary(); - - private static readonly IDictionary PatchEmotes = new Dictionary { - { Patch.ARR, ":2x:" }, - { Patch.HW, ":3x:" }, - { Patch.SB, ":4x:" }, - { Patch.SHB, ":5x:" }, - { Patch.EW, ":6x:" }, - { Patch.DT, ":7x:" }, - }.VerifyEnumDictionary(); - - public static IList HuntMaps(this Patch patch) => PatchHuntMaps[patch]; - - public static uint MaxMarks(this Patch patch) { - if (patch == Patch.ARR) return 17; - return (uint)patch.HuntMaps().Sum(territory => 2 * territory.Instances()); - } - - public static string Emote(this Patch patch) => PatchEmotes[patch]; -} diff --git a/ScoutHelper/Models/Territory.cs b/ScoutHelper/Models/Territory.cs deleted file mode 100644 index 01e66f9..0000000 --- a/ScoutHelper/Models/Territory.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using ScoutHelper.Config; -using ScoutHelper.Utils.Functional; -using static ScoutHelper.Models.Territory; -using static ScoutHelper.Utils.Utils; - -namespace ScoutHelper.Models; - -public enum Territory { - // HW - CoerthasWesternHighlands, - TheSeaOfClouds, - AzysLla, - TheDravanianForelands, - TheDravanianHinterlands, - TheChurningMists, - - // SB - TheFringes, - ThePeaks, - TheLochs, - TheRubySea, - Yanxia, - TheAzimSteppe, - - // SHB - Lakeland, - Kholusia, - AmhAraeng, - IlMheg, - TheRaktikaGreatwood, - TheTempest, - - // EW - Labyrinthos, - Thavnair, - Garlemald, - MareLamentorum, - Elpis, - UltimaThule, - - // DT - Urqopacha, - Kozamauka, - YakTel, - Shaaloani, - HeritageFound, - LivingMemory, -} - -public static class TerritoryExtensions { - private static readonly IDictionary TerritoryNames = new Dictionary() { - { CoerthasWesternHighlands, "coerthas western highlands" }, - { TheSeaOfClouds, "the sea of clouds" }, - { AzysLla, "azys lla" }, - { TheDravanianForelands, "the dravanian forelands" }, - { TheDravanianHinterlands, "the dravanian hinterlands" }, - { TheChurningMists, "the churning mists" }, - - { TheFringes, "the fringes" }, - { ThePeaks, "the peaks" }, - { TheLochs, "the lochs" }, - { TheRubySea, "the ruby sea" }, - { Yanxia, "yanxia" }, - { TheAzimSteppe, "the azim steppe" }, - - { Lakeland, "lakeland" }, - { Kholusia, "kholusia" }, - { AmhAraeng, "amh araeng" }, - { IlMheg, "il mheg" }, - { TheRaktikaGreatwood, "the rak'tika greatwood" }, - { TheTempest, "the tempest" }, - - { Labyrinthos, "labyrinthos" }, - { Thavnair, "thavnair" }, - { Garlemald, "garlemald" }, - { MareLamentorum, "mare lamentorum" }, - { Elpis, "elpis" }, - { UltimaThule, "ultima thule" }, - - { Urqopacha, "urqopacha" }, - { Kozamauka, "kozama'uka" }, - { YakTel, "yak t'el" }, - { Shaaloani, "shaaloani" }, - { HeritageFound, "heritage found" }, - { LivingMemory, "living memory" }, - }.VerifyEnumDictionary(); - - private static readonly IDictionary DefaultTerritoryInstances = - GetEnumValues() - .Select(territory => (territory, 1U)) - .Concat(Constants.LatestPatchInstances) - .ToDict(); - - private static Configuration _conf = null!; - - private static IDictionary _territoryToId = null!; - private static IDictionary _idToTerritory = null!; - - public static void SetTerritoryInstances( - Configuration conf, - IEnumerable<(Territory territory, uint id)> territoryIds - ) { - if (_conf is not null) - throw new Exception("cannot set territory instance dictionary after plugin initialization."); - - _conf = conf; - _territoryToId = territoryIds.ToDict(); - _idToTerritory = _territoryToId.Flip(); - } - - public static Territory AsTerritory(this uint territoryId) => _idToTerritory[territoryId]; - - public static string Name(this Territory territory) => TerritoryNames[territory]; - - public static uint DefaultInstances(this Territory territory) => DefaultTerritoryInstances[territory]; - - public static uint Instances(this Territory territory) => - _conf - .Instances - .GetValueOrDefault( - _territoryToId.MaybeGet(territory).GetValueOrDefault(0U), - 0U - ); -} diff --git a/ScoutHelper/Plugin.cs b/ScoutHelper/Plugin.cs index 7be4d4e..09fb274 100644 --- a/ScoutHelper/Plugin.cs +++ b/ScoutHelper/Plugin.cs @@ -11,6 +11,7 @@ using ScoutHelper.Managers; using ScoutHelper.Utils; using ScoutHelper.Windows; +using XIVHuntUtils.Managers; namespace ScoutHelper; diff --git a/ScoutHelper/ScoutHelper.csproj b/ScoutHelper/ScoutHelper.csproj index 1996ba7..cd41b4d 100644 --- a/ScoutHelper/ScoutHelper.csproj +++ b/ScoutHelper/ScoutHelper.csproj @@ -10,8 +10,12 @@ - + + + + + diff --git a/ScoutHelper/Utils/CollectionExtensions.cs b/ScoutHelper/Utils/CollectionExtensions.cs index eb54723..c9a5360 100644 --- a/ScoutHelper/Utils/CollectionExtensions.cs +++ b/ScoutHelper/Utils/CollectionExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using ScoutHelper.Utils.Functional; +using DitzyExtensions.Functional; namespace ScoutHelper; diff --git a/ScoutHelper/Utils/Functional/AccResult.cs b/ScoutHelper/Utils/Functional/AccResult.cs deleted file mode 100644 index 354946e..0000000 --- a/ScoutHelper/Utils/Functional/AccResult.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace ScoutHelper.Utils.Functional; - -public class AccResults : IAccResults { - private readonly T _value; - private readonly IList _errors; - - public T Value => _value; - - public IEnumerable Errors => _errors; - - internal AccResults(T value, IEnumerable errors) { - _value = value; - _errors = errors.ToImmutableList(); - } - - public static implicit operator AccResults(T value) => AccResults.From(value); - public static implicit operator AccResults(E error) => AccResults.From(default, error.AsSingletonList()); -} - -public static class AccResults { - public static AccResults From(T value) => new(value, new List()); - public static AccResults From(T value, IEnumerable errors) => new(value, errors); -} diff --git a/ScoutHelper/Utils/Functional/FunctionalExtensions.cs b/ScoutHelper/Utils/Functional/FunctionalExtensions.cs deleted file mode 100644 index 1624a46..0000000 --- a/ScoutHelper/Utils/Functional/FunctionalExtensions.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using CSharpFunctionalExtensions; - -namespace ScoutHelper.Utils.Functional; - -public static class FunctionalExtensions { - #region maybe - - public static IEnumerable SelectMaybe(this IEnumerable> source) => - source.SelectMaybe(value => value); - - public static IEnumerable SelectMaybe(this IEnumerable source, Func> selector) => - source.SelectWhere( - value => { - var result = selector.Invoke(value); - return (result.HasValue, result); - } - ) - .Select(result => result.Value); - - public static IEnumerable> SelectOverMaybe(this IEnumerable> source, Func transform) => - source.Select(maybeValue => maybeValue.Select(transform)); - - public static IEnumerable> SelectManyOverMaybe(this IEnumerable> source, Func> transform) => - source.Select(maybeValue => maybeValue.SelectMany(transform)); - - public static Maybe MaybeGet(this IDictionary source, K key) { - if (source.TryGetValue(key, out var value)) { - return value; - } - return Maybe.None; - } - - public static Maybe<(T first, U second)> Join(this Maybe value, U secondValue) => - value.Select(t => (t, secondValue)); - - public static Maybe<(T first, U second)> Join(this Maybe value, Maybe secondValue) => - value.SelectMany(t => secondValue.Select(u => (t, u))); - - #endregion - - #region results - - public static AccResults, string> SelectResults(this IEnumerable> source) => - source.SelectResults(result => result); - - public static AccResults, string> SelectResults( - this IEnumerable source, - Func> selector - ) => - source.SelectResults( - value => selector.Invoke(value).Match( - Result.Success, - Result.Failure - ) - ); - - public static AccResults, E> SelectResults(this IEnumerable> source) => - source.SelectResults(result => result); - - public static AccResults, E> SelectResults( - this IEnumerable source, - Func> selector - ) => - source.BindResults( - result => selector - .Invoke(result) - .Map(AccResults.From) - ); - - public static AccResults, E> BindResults( - this IEnumerable source, - Func, E>> transform - ) => - source.Reduce( - (acc, value) => { - transform.Invoke(value).Match( - success => { - acc.results.Add(success.Value); - acc.errors.AddRange(success.Errors); - }, - error => acc.errors.Add(error) - ); - return acc; - }, - (results: new List(), errors: new List()) - ) - .ToAccResult(results => (IEnumerable)results); - - public static AccResults, E> SelectValues( - this AccResults, E> source, - Func selector - ) { - var newValue = source.Value.Select(selector); - return new AccResults, E>(newValue, source.Errors); - } - - public static AccResults, E> SelectResults( - this AccResults, E> source, - Func> selector - ) { - var newAccResults = source.Value.SelectResults(selector); - return new AccResults, E>(newAccResults.Value, source.Errors.Concat(newAccResults.Errors)); - } - - public static AccResults Join( - this AccResults source, - AccResults secondSource, - Func combiner - ) => - new( - combiner.Invoke(source.Value, secondSource.Value), - source.Errors.Concat(secondSource.Errors) - ); - - public static AccResults WithValue(this AccResults source, Func valueModifier) => - new(valueModifier.Invoke(source.Value), source.Errors); - - public static AccResults ForEachError(this AccResults source, Action action) { - source.Errors.ForEach(action); - return source; - } - - public static AccResults ForEachValue(this AccResults source, Action action) - where TWrap : IEnumerable { - source.Value.ForEach(action); - return source; - } - - public static (T, IEnumerable) AsPair(this AccResults source) { - return (source.Value, source.Errors); - } - - public static AccResults ToAccResult(this (T, IEnumerable) pair) => new(pair.Item1, pair.Item2); - - public static AccResults ToAccResult(this (T, IEnumerable) pair, Func transformer) => - new(transformer.Invoke(pair.Item1), pair.Item2); - - public static AccResults ToAccResult(this T value) => AccResults.From(value); - - public static AccResults, E> SelectMany( - this IEnumerable source, - Func> selector - ) { - return source.Select(selector) - .Reduce( - (acc, results) => { - acc.values.Add(results.Value); - acc.errors.AddRange(results.Errors); - return acc; - }, - (values: new List(), errors: new List()) - ) - .ToAccResult(values => (IEnumerable)values); - } - - #endregion - - #region pure functions - - public static ACC Reduce(this IEnumerable source, Func reducer, ACC initial) { - var acc = initial; - source.ForEach(value => { acc = reducer.Invoke(acc, value); }); - return acc; - } - - public static IEnumerable Sequence(this Range range) { - for (int i = range.Start.Value; i < range.End.Value; i++) { - yield return i; - } - } - - public static Func AsFunc(this Action action) => - () => { - action(); - return Nothing.Value; - }; - - public struct Nothing { - public static readonly Nothing Value = new(); - } - - #endregion -} diff --git a/ScoutHelper/Utils/Functional/IAccResult.cs b/ScoutHelper/Utils/Functional/IAccResult.cs deleted file mode 100644 index 4f13dc5..0000000 --- a/ScoutHelper/Utils/Functional/IAccResult.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace ScoutHelper.Utils.Functional; - -public interface IAccResults { - T Value { get; } - IEnumerable Errors { get; } -} diff --git a/ScoutHelper/Utils/Utils.cs b/ScoutHelper/Utils/Utils.cs index 963259b..37f2a71 100644 --- a/ScoutHelper/Utils/Utils.cs +++ b/ScoutHelper/Utils/Utils.cs @@ -7,7 +7,8 @@ using CSharpFunctionalExtensions; using Lumina.Text; using Newtonsoft.Json; -using ScoutHelper.Models; +using XIVHuntUtils.Models; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Utils; diff --git a/ScoutHelper/Windows/ConfigWindow.cs b/ScoutHelper/Windows/ConfigWindow.cs index 449745b..d8693b0 100644 --- a/ScoutHelper/Windows/ConfigWindow.cs +++ b/ScoutHelper/Windows/ConfigWindow.cs @@ -10,14 +10,15 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; +using DitzyExtensions.Functional; using ImGuiNET; using ScoutHelper.Config; using ScoutHelper.Localization; -using ScoutHelper.Managers; -using ScoutHelper.Models; using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; +using XIVHuntUtils.Managers; +using XIVHuntUtils.Models; using static ScoutHelper.Utils.Utils; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Windows; diff --git a/ScoutHelper/Windows/ImGuiPlus.cs b/ScoutHelper/Windows/ImGuiPlus.cs index dbce32a..38cca16 100644 --- a/ScoutHelper/Windows/ImGuiPlus.cs +++ b/ScoutHelper/Windows/ImGuiPlus.cs @@ -5,9 +5,9 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using DitzyExtensions.Functional; using ImGuiNET; using OtterGui.Widgets; -using ScoutHelper.Utils.Functional; namespace ScoutHelper.Windows; diff --git a/ScoutHelper/Windows/MainWindow.cs b/ScoutHelper/Windows/MainWindow.cs index 4592132..d6dec8f 100644 --- a/ScoutHelper/Windows/MainWindow.cs +++ b/ScoutHelper/Windows/MainWindow.cs @@ -10,15 +10,16 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Dalamud.Utility.Numerics; +using DitzyExtensions.Functional; using ImGuiNET; using OtterGui.Widgets; using ScoutHelper.Config; using ScoutHelper.Localization; using ScoutHelper.Managers; -using ScoutHelper.Models; using ScoutHelper.Utils; -using ScoutHelper.Utils.Functional; +using XIVHuntUtils.Models; using static ScoutHelper.Utils.Utils; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelper.Windows; @@ -499,8 +500,8 @@ private void PushLatestMobsToTurtle() { } private void GenerateLink( - Func, AccResults> linkGenerator, - Action, AccResults> onSuccess + Func, AccumulatedResults> linkGenerator, + Action, AccumulatedResults> onSuccess ) { GetTrainMobs(out var trainList) .Map(linkGenerator) diff --git a/ScoutHelper/packages.lock.json b/ScoutHelper/packages.lock.json index 003e81f..aa33059 100644 --- a/ScoutHelper/packages.lock.json +++ b/ScoutHelper/packages.lock.json @@ -4,9 +4,9 @@ "net8.0-windows7.0": { "CSharpFunctionalExtensions": { "type": "Direct", - "requested": "[2.40.3, )", - "resolved": "2.40.3", - "contentHash": "G+t+bf39aixVFN3O3Q5LoE0zy3o8QlV3mv3ATw76XwDRWkE9eFRLzZdx9HH9fgbPpasfP4kr8cnnXIP+Z7GcrQ==" + "requested": "[2.42.5, )", + "resolved": "2.42.5", + "contentHash": "Do9Q7Nyfx2G4HRt96aB6yoP5Z9ic3jqBHLrf35tULzsWlFoBlkQZT62aXCiv7UmVmF56OUR+a3JoSSs140hfHQ==" }, "DalamudPackager": { "type": "Direct", @@ -14,6 +14,15 @@ "resolved": "2.1.13", "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" }, + "DitzyExtensions": { + "type": "Direct", + "requested": "[1.2.0, )", + "resolved": "1.2.0", + "contentHash": "j0FCJW/xrKAvAaj3jjD9+aYcJZrZ6TsYYGV650siiuvi5GzZ5zz8pe3MsJO0hy+k69RXnZW8ErIKezQZXzmgYQ==", + "dependencies": { + "CSharpFunctionalExtensions": "2.42.5" + } + }, "DotNet.ReproducibleBuilds": { "type": "Direct", "requested": "[1.1.1, )", @@ -56,6 +65,16 @@ "Microsoft.Extensions.Options": "8.0.0" } }, + "XIVHuntUtils": { + "type": "Direct", + "requested": "[1.1.0, )", + "resolved": "1.1.0", + "contentHash": "yNlpsEYgqM4Bj8z5sjRMBJCC5nI0vlGe4jlpcIFhjTfzi5ultGfMNgS+weFQ8jSVoG/R+qbHuwmZK4TR909G7Q==", + "dependencies": { + "CSharpFunctionalExtensions": "2.42.5", + "DitzyExtensions": "1.2.0" + } + }, "JetBrains.Annotations": { "type": "Transitive", "resolved": "2023.3.0", diff --git a/ScoutHelperTests/Managers/MobManagerTest.cs b/ScoutHelperTests/Managers/MobManagerTest.cs deleted file mode 100644 index 58996df..0000000 --- a/ScoutHelperTests/Managers/MobManagerTest.cs +++ /dev/null @@ -1,136 +0,0 @@ -using CSharpFunctionalExtensions; -using Dalamud.Game; -using Dalamud.Plugin.Services; -using FluentAssertions; -using FsCheck; -using FsCheck.Xunit; -using JetBrains.Annotations; -using Lumina.Excel.GeneratedSheets2; -using Moq; -using ScoutHelper; -using ScoutHelper.Managers; -using ScoutHelper.Utils; -using ScoutHelperTests.TestUtils.FsCheck; -using ScoutHelperTests.TestUtils.MoqHelpers; -using static ScoutHelperTests.TestUtils.FsCheck.FsCheckUtils; -using NameList = System.Collections.Generic.IList<(uint mobId, string mobName)>; - -namespace ScoutHelperTests.Managers; - -[TestSubject(typeof(MobManager))] -public class MobManagerTest { - private readonly Mock _log = new(MockBehavior.Strict); - private readonly Mock _dataManager = new(MockBehavior.Strict); - - [Property] - public Property IndexContainsAllElements() => ForAll( - Arb.Default.UInt32().DistinctListOfPairsWith(Arbs.String()), - (npcNames) => { - // DATA - var npcNameSheet = MockExcelSheet.Create() - .AddRows(npcNames.Select(name => MockBNpcName.Create(name.Item1, name.Item2))); - var notoriousMonsterSheet = MockExcelSheet.Create() - .AddRows(npcNames.Select(name => MockNotoriousMonster.Create(name.Item1, name.Item1))); - - // GIVEN - _log.Setup(log => log.Debug(It.IsAny())); - _dataManager.Setup(dm => dm.GetExcelSheet(It.IsAny())) - .Returns(npcNameSheet); - _dataManager.Setup(dm => dm.GetExcelSheet(It.IsAny())) - .Returns(notoriousMonsterSheet); - - // WHEN - var mobManager = new MobManager(_log.Object, _dataManager.Object); - - // THEN - _dataManager.Verify(manager => manager.GetExcelSheet(ClientLanguage.English)); - _dataManager.Verify(manager => manager.GetExcelSheet(ClientLanguage.English)); - _log.Verify(log => log.Debug("Building mob data from game files...")); - _log.Verify(log => log.Debug("Mob data built.")); - _dataManager.VerifyNoOtherCalls(); - _log.VerifyNoOtherCalls(); - - npcNames.ForEach( - entry => { - var mobId = entry.Item1; - var mobName = entry.Item2; - mobManager.GetMobName(mobId).Should().Be(Maybe.From(mobName.Lower())); - mobManager.GetMobId(mobName).Should().Be(Maybe.From(mobId)); - } - ); - } - ); - - [Property] - public Property IndexHandlesDuplicates() => ForAll( - DupIndexItemsArb(), - inputs => { - // DATA - var npcNames = inputs.names - .Concat(inputs.dupNames) - .AsList(); - var numDupNames = inputs.dupNames.DistinctBy(name => name.mobName).Count(); - - var npcNameSheet = MockExcelSheet .Create() - .AddRows(npcNames.Select(name => MockBNpcName.Create(name.mobId, name.mobName))); - var notoriousMonsterSheet = MockExcelSheet.Create() - .AddRows(npcNames.Select(name => MockNotoriousMonster.Create(name.mobId, name.mobId))); - - // GIVEN - _log.Reset(); - _dataManager.Reset(); - - _log.Setup(log => log.Debug(It.IsAny())); - _log.Setup(log => log.Debug(It.IsAny(), It.IsAny())); - _dataManager.Setup(dm => dm.GetExcelSheet(It.IsAny())) - .Returns(npcNameSheet); - _dataManager.Setup(dm => dm.GetExcelSheet(It.IsAny())) - .Returns(notoriousMonsterSheet); - - // WHEN - var mobManager = new MobManager(_log.Object, _dataManager.Object); - - // THEN - _dataManager.Verify(manager => manager.GetExcelSheet(ClientLanguage.English)); - _dataManager.Verify(manager => manager.GetExcelSheet(ClientLanguage.English)); - _log.Verify(log => log.Debug("Building mob data from game files...")); - _log.Verify(log => log.Debug("Mob data built.")); - _log.Verify( - log => log.Debug( - It.Is(msg => msg.StartsWith("Duplicate mobs found for name")), - It.IsAny() - ), - Times.Exactly(numDupNames) - ); - _dataManager.VerifyNoOtherCalls(); - _log.VerifyNoOtherCalls(); - - inputs.names.ForEach( - name => { - mobManager.GetMobName(name.mobId).Should().Be(Maybe.From(name.mobName.Lower())); - mobManager.GetMobId(name.mobName).Should().Be(Maybe.From(name.mobId)); - } - ); - - inputs.dupNames.ForEach( - name => { mobManager.GetMobName(name.mobId).Should().Be(Maybe.None); } - ); - } - ); - - private static Arbitrary<(NameList names, NameList dupNames)> DupIndexItemsArb() { - return Arb.Default - .UInt32() - .DistinctListOfPairsWith(Arbs.String()) - .NonEmpty() - .SelectMany( - names => Arb.Default.UInt32() - .Where(mobId => names.All(name => mobId != name.Item1)) - .ZipWith(Gen.Elements<(uint, string name)>(names).KeepSecond()) - .NonEmptyListOf() - .Select(dupNames => dupNames.WithDistinctFirst()) - .Select(dupNames => (names, dupNames.AsList())) - ) - .ToArbitrary(); - } -} diff --git a/ScoutHelperTests/TestUtils/FsCheck/Arbs.cs b/ScoutHelperTests/TestUtils/FsCheck/Arbs.cs index 8cadb91..34499c9 100644 --- a/ScoutHelperTests/TestUtils/FsCheck/Arbs.cs +++ b/ScoutHelperTests/TestUtils/FsCheck/Arbs.cs @@ -2,8 +2,9 @@ using CSharpFunctionalExtensions; using FsCheck; using ScoutHelper; -using ScoutHelper.Models; using ScoutHelper.Utils; +using XIVHuntUtils.Models; +using TrainMob = ScoutHelper.Models.TrainMob; namespace ScoutHelperTests.TestUtils.FsCheck; diff --git a/ScoutHelperTests/Utils/Functional/FunctionalExtensionsTest.cs b/ScoutHelperTests/Utils/Functional/FunctionalExtensionsTest.cs deleted file mode 100644 index 31c1e17..0000000 --- a/ScoutHelperTests/Utils/Functional/FunctionalExtensionsTest.cs +++ /dev/null @@ -1,91 +0,0 @@ -using CSharpFunctionalExtensions; -using FluentAssertions; -using FsCheck; -using FsCheck.Xunit; -using JetBrains.Annotations; -using ScoutHelper; -using ScoutHelper.Utils.Functional; -using ScoutHelperTests.TestUtils.FsCheck; -using static ScoutHelperTests.TestUtils.FsCheck.FsCheckUtils; - -namespace ScoutHelperTests.Utils.Functional; - -[TestSubject(typeof(FunctionalExtensions))] -public class FunctionalExtensionsTest { - [Property] - public Property MaybeGet_MissingKey() => ForAll( - Arbs.String().DictWith(Arbs.String()), - Arbs.String(), - (arbitraryDict, lookupKey) => { - // DATA - var dict = arbitraryDict.Without(lookupKey); - - // WHEN - var actual = dict.MaybeGet(lookupKey); - - // THEN - actual.Should().Be(Maybe.None); - } - ); - - [Property] - public Property MaybeGet_ContainsKey() => ForAll( - Arbs.String().DictWith(Arbs.String()), - Arbs.String(), - Arbs.String(), - (arbitraryDict, lookupKey, lookupValue) => { - // DATA - var dict = arbitraryDict.With((lookupKey, lookupValue)); - - // WHEN - var actual = dict.MaybeGet(lookupKey); - - // THEN - actual.Should().Be(Maybe.From(lookupValue)); - } - ); - - [Property] - public Property MaybeGet_EmptyDict() => ForAll( - Arbs.String(), - lookupKey => { - // DATA - var dict = new Dictionary(); - - // WHEN - var actual = dict.MaybeGet(lookupKey); - - // THEN - actual.Should().Be(Maybe.None); - } - ); - - [Property] - public Property SelectMaybe_ExistingList() => ForAll( - Arbs.String().ToMaybeArb().NonEmptyListOf(), - (list) => { - // DATA - var expectedValues = list - .Where(maybe => !Maybe.None.Equals(maybe)) - .Select(value => value.Value); - - // WHEN - var actual = list.SelectMaybe(); - - // THEN - actual.Should().BeEquivalentTo(expectedValues); - } - ); - - // select maybe - // reduce - - // == result == - // as pair - // for each error - // join - // select results - // select values - // to accresult - // with value -}