diff --git a/apps/backend/Jobs/Data/DataConnectedRealmIndexJob.cs b/apps/backend/Jobs/Data/DataConnectedRealmIndexJob.cs index c99cf210a..0607941a1 100644 --- a/apps/backend/Jobs/Data/DataConnectedRealmIndexJob.cs +++ b/apps/backend/Jobs/Data/DataConnectedRealmIndexJob.cs @@ -19,11 +19,17 @@ public override async Task Run(string[] data) var uri = GenerateUri(region, ApiNamespace.Dynamic, ApiPath); var result = await GetUriAsJsonAsync(uri, useLastModified: false); + var jobParams = new List(result.Data.ConnectedRealms.Count); foreach (var href in result.Data.ConnectedRealms) { var match = RealmIdRegex.Match(href.Href); - await JobRepository.AddJobAsync(JobPriority.Low, JobType.DataConnectedRealm, ((int)region).ToString(), match.Groups[1].Value); + jobParams.Add([ + ((int)region).ToString(), + match.Groups[1].Value + ]); } + + await JobRepository.AddJobsAsync(JobPriority.Low, JobType.DataConnectedRealm, jobParams); } } } diff --git a/apps/backend/Services/SchedulerService.cs b/apps/backend/Services/SchedulerService.cs index ba8279c79..c3bccbe61 100644 --- a/apps/backend/Services/SchedulerService.cs +++ b/apps/backend/Services/SchedulerService.cs @@ -119,7 +119,8 @@ protected override async void TimerCallback(object state) if (characterResults.Length > 0) { var resultData = characterResults - .Select(cr => JsonSerializer.Serialize(cr, _jsonSerializerOptions)); + .Select(cr => JsonSerializer.Serialize(cr, _jsonSerializerOptions)) + .ToList(); // Queue character jobs Logger.Information("Queueing {0} character job(s)", characterResults.Length); diff --git a/apps/frontend/components/character-table/row/RaiderIo.svelte b/apps/frontend/components/character-table/row/RaiderIo.svelte index 531b0578d..99c2c9d76 100644 --- a/apps/frontend/components/character-table/row/RaiderIo.svelte +++ b/apps/frontend/components/character-table/row/RaiderIo.svelte @@ -1,9 +1,9 @@ @@ -66,7 +68,7 @@ } -{#if (userHas && $recipesState.showCollected) || (!userHas && $recipesState.showUncollected)} +{#if (userHas && $recipesState.showCollected) || (!userHas && $recipesState.showUncollected && !isIgnored)} abilityId), ]; const abilityUserHas = userState.recipes.hasAbility[ability.id]; + abilityIds.forEach((_abilityId, index) => { const userHas = abilityUserHas[index]; if ( (userHas && $recipesState.showCollected) || - (!userHas && $recipesState.showUncollected) + (!userHas && $recipesState.showUncollected && !isIgnored) ) { anyShown = true; return; diff --git a/apps/frontend/user-home/state/user/derived.svelte.ts b/apps/frontend/user-home/state/user/derived.svelte.ts index 6685f7b87..f74de484e 100644 --- a/apps/frontend/user-home/state/user/derived.svelte.ts +++ b/apps/frontend/user-home/state/user/derived.svelte.ts @@ -265,6 +265,14 @@ export class DataUserDerived { (have) => have ).length; + // don't add stats for ignored spells that the user doesn't know + if ( + abilityHave === 0 && + wowthingData.manual.ignoredSkillLineAbilitySpellIds.has(ability.spellId) + ) { + continue; + } + if ( !settingsState.value.collections.hideFuture || categoryIndex <= Constants.expansion @@ -697,9 +705,18 @@ export class DataUserDerived { charChore.status === QuestStatus.InProgress && charChore.quest?.objectives?.length > 0 ) { - const lastObjective = charChore.quest.objectives.at(-1); - charChore.progressCurrent = lastObjective.have; - charChore.progressTotal = lastObjective.need; + if (chore.sumObjectives) { + charChore.progressCurrent = 0; + charChore.progressTotal = 0; + for (const objective of charChore.quest.objectives) { + charChore.progressCurrent += objective.have; + charChore.progressTotal += objective.need; + } + } else { + const lastObjective = charChore.quest.objectives.at(-1); + charChore.progressCurrent = lastObjective.have; + charChore.progressTotal = lastObjective.need; + } } if ( @@ -721,9 +738,11 @@ export class DataUserDerived { charChore.status === QuestStatus.InProgress && charChore.quest.objectives?.length > 0 ) { - const lastObjective = charChore.quest.objectives.at(-1); - charChore.progressCurrent = lastObjective.have; - charChore.progressTotal = lastObjective.need; + if (!charChore.progressTotal) { + const lastObjective = charChore.quest.objectives.at(-1); + charChore.progressCurrent = lastObjective.have; + charChore.progressTotal = lastObjective.need; + } charChore.statusTexts = this.getObjectivesText(charChore.quest.objectives); } diff --git a/apps/frontend/utils/get-progress.ts b/apps/frontend/utils/get-progress.ts index cddb50008..1ca650abe 100644 --- a/apps/frontend/utils/get-progress.ts +++ b/apps/frontend/utils/get-progress.ts @@ -224,6 +224,12 @@ export default function getProgress( haveThis = questStatuses.at(-1); descriptionText[dataIndex] = `${haveQuests} / ${data.ids.length}`; + + // if there's only one campaign/questline, use its data for overall + if (datas.length === 1) { + total = data.ids.length; + have = haveQuests - (haveThis ? 1 : 0); + } } else if (group.type === 'item') { haveThis = data.ids.some((id) => character.getItemCount(id) > 0); } else if (group.type === 'dragon-racing') { diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index ff07e7576..d563ce87c 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -24,9 +24,9 @@ const config = defineConfig({ emitCss: production, preprocess: sveltePreprocess({ // it can't find the file in the same directrory otherwise, wtf - typescript: { - tsconfigFile: 'tsconfig.json', - }, + // typescript: { + // tsconfigFile: `./apps/frontend/tsconfig.json`, + // }, // scss: { // prependData: ` // @import 'scss/mixins.scss'; diff --git a/apps/tool/Data/IgnoredSkillLineAbilities.cs b/apps/tool/Data/IgnoredSkillLineAbilities.cs index 2c1f38d9a..d5b7f26f9 100644 --- a/apps/tool/Data/IgnoredSkillLineAbilities.cs +++ b/apps/tool/Data/IgnoredSkillLineAbilities.cs @@ -17,293 +17,4 @@ public static partial class Hardcoded 50551, // Rockslide Shake that doesn't exist ]; - - public static readonly HashSet IgnoredSkillLineAbilitySpells = - [ - // Alchemy - 17579, // Greater Holy Protection Potion - 28577, // Major Holy Protection Potion - 54020, // Transmute: Eternal Might - 156567, // WoD Draenor Armor Flask (never implemented) - 156588, // WoD Alchemical Catalyst - Fireweed - 156589, // WoD Alchemical Catalyst - Flytrap - 156590, // WoD Alchemical Catalyst - Starflower - 156591, // WoD Primal Alchemy (never implemented) - 156592, // WoD Alchemical Catalyst - Orchid - 156593, // WoD Alchemical Catalyst - Lotus - 168042, // WoD Alchemical Catalyst (dupe for max results?) - 370771, // Dragon Isles Alchemy Troubleshooting Test Recipe (DNT) - 371635, // Demonstration Item Recipe - 430606, // TWW ?? - 433271, // TWW ?? - 433604, // TWW ?? - 433605, // TWW ?? - 433606, // TWW ?? - 433607, // TWW ?? - 433608, // TWW ?? - 430610, // TWW ?? - 1230772, // Mid ?? - 1230871, // Mid ?? - 1230879, // Mid ?? - 1230880, // Mid ?? - 1230881, // Mid ?? - 1230882, // Mid ?? - 1230884, // Mid ?? - 1233131, // Mid ?? - 1233139, // Mid ?? - - // Blacksmithing - 2671, // Rough Bronze Bracers - 8366, // Ironforge Chain - 8368, // Ironforge Gauntlets - 9942, // Mithril Scale Gloves - 9957, // Orcish War Leggings - // 9972, // Ornate Mithril Breastplate - // 9979, // Ornate Mithril Boots - // 9980, // Ornate Mithril Helm - 16960, // Thorium Greatsword - 16965, // Bleakwood Hew - 16967, // Inlaid Thorium Hammer - 16980, // Rune Edge - 16986, // Blood Talon - 34529, // Nether Chain Shirt - 34530, // TBC Twisting Nether Chain Shirt - 55183, // WotLK Corroded Saronite Edge - 55184, // WotLK Corroded Saronite Woundbringer - 55186, // WotLK Chestplate of Conquest - 55187, // WotLK Legplates of Conquest - 409224, // Reclaimed Gauntlet Chassis (temporary legendary craft) - - // Blacksmithing - MoP Spiritguard - 122568, // Helm - 122569, // Shoulders - 122570, // Breastplate - 122571, // Gauntlets - 122572, // Legplates - 122573, // Bracers - 122574, // Boots - 122575, // Belt - - // Blacksmithing - MoP Lightsteel - 122584, // Helm - 122585, // Shoulders - 122586, // Breastplate - 122587, // Gauntlets - 122588, // Legplates - 122589, // Bracers - 122590, // Boots - 122591, // Belt - - // Blacksmithing - MoP Masterwork Lightsteel - 122608, // Helm - 122609, // Shoulders - 122610, // Breastplate - 122611, // Gauntlets - 122612, // Legplates - 122613, // Bracers - 122614, // Boots - 122615, // Belt - - // Blacksmithing - MoP ?? - 122600, - 122601, - 122602, - 122603, - 122604, - 122605, - 122606, - 122607, - - // Blacksmithing - Draenic Steel - 153605, - 153606, - 153607, - 153608, - 153609, - 153610, - 153611, - 153612, - 153627, - 153628, - 153629, - 153630, - 153631, - 153643, - 153644, - 153645, - 153646, - 153647, - 153648, - 153649, - 153650, - 153651, - 153652, - 153653, - 153654, - 153655, - 153656, - 153657, - 153658, - 153659, - 153660, - 153661, - 153663, - 153664, - 153665, - 153666, - 153667, - 153668, - - // Cooking - 145167, // Grand Noodle Cart Kit - 145170, // Grand Deluxe Noodle Cart Kit - 145197, // Grand Pandaren Treasure Noodle Cart Kit - 169693, // Whole Pot-Roasted Elekk - 169696, // Marinated Elekk Steak - 169699, // Seasoned Elekk Ribeye - - // Enchanting - 28021, // Arcane Dust - 44612, // WotLK Greater Blasting - 62257, // WotLK Titanguard - 343681, // Shadowlands ?? - 422338, // Shalasar's Sophic Vellum (temporary legendary craft) - - // Enchanting - WoD mess - 173716, - 173717, - 173718, - 174979, - 173720, - 175076, - 173721, - 173722, - 173723, - 175072, - 175071, - 173719, - 173724, - 181870, - 175070, - 173725, - 173726, - 175085, - 173727, - 173728, - 173729, - 175086, - 175078, - 173730, - 173731, - 173732, - 175074, - 177355, - 178241, // Other Temporal Crystal? - - // Engineering - 12900, // Mobile Alarm - 30342, // TBC Red Smoke Flare - 30343, // TBC Blue Smoke Flare - 162208, // Ultimate Gnomish Army Knife (dupe for BoE?) - 178242, // Gearspring Parts (dupe for max results?) - 407170, // Inspired Order Recalibrator (temporary legendary craft) - 473408, // Housing Crafting Test Recipe - 1229918, // Mid ?? - 1229925, // Mid ?? - 1229929, // Mid ?? - 1261938, // Mid ?? - - // Fishing - 1231090, // Mid Rootland Crab Lure - - // Inscription - 127475, // MoP Incarnadine Ink - 130407, // MoP Mystery of the Mists - 176513, // WoD Draenor Merchant Order - 178240, // WoD War Paints - 178550, // WoD Borrow Draenic Mortar - 343687, // Shadowlands ?? - 422337, // Lydaria's Binding Rune (temporary legendary craft) - 1230063, // Mid ?? - - // Jewelcrafting - 25614, // TBC Silver Rose Pendant - 26918, // TBC Arcanite Sword Pendant - 26920, // TBC Blood Crown - 32810, // TBC Primal Stone Statue - 73233, // Cata Mystic Alicite - 73259, // Cata Resolute Hessonite - 407161, // Immaculate Coalescing Dracothyst (temporary legendary craft) - - // Leatherworking - 10550, // Nightscape Cloak - 10619, // Classic Dragonscale Gauntlets - 10621, // Classic Wolfshead Helm - 19106, // Onyxia Scale Breastplate - 35575, // TBC Ebon Netherscale Breastplate - 35576, // TBC Ebon Netherscale Belt - 35577, // TBC Ebon Netherscale Bracers - 35580, // TBC Netherstrike Breastplate - 35582, // TBC Netherstrike Belt - 35584, // TBC Netherstrike Bracers - 35585, // TBC Windhawk Hauberk - 35587, // TBC Windhawk Belt - 35588, // TBC Windhawk Bracers - 35589, // TBC Primalstrike Vest - 35590, // TBC Primalstrike Belt - 35591, // TBC Primalstrike Bracers - 44359, // TBC Quiver of a Thousand Feathers - 102366, // Mist-Touched Leather (combining 5 pieces?) - 171713, // Burnished Leather (dupe for max results?) - 173416, // Small Football (never implemented?) - 293135, // Legion Drum of Primal Night (quest?) - 422330, // Erden's Glowspore Grip (temporary legendary craft) - - // Mining - // 389465, // Severite Seam - // 389458, // Draconium Seam - 423882, // Overload Test Deposit - - // Tailoring - 7636, // Green Woolen Robe - 8778, // Boots of Darkness - 12062, // Stormcloth Pants - 12063, // Stormcloth Gloves - 12068, // Stormcloth Vest - 12083, // Stormcloth Headband - 12087, // Stormcloth Shoulders - 12090, // Stormcloth Boots - 36665, // Pattern: Netherflame Robe - 36667, // Pattern: Netherflame Belt - 36668, // Pattern: Netherflame Boots - 36669, // Pattern: Lifeblood Leggings - 36670, // Pattern: Lifeblood Belt - 36672, // Pattern: Lifeblood Bracers - - 104115, // MoP Release Fire Spirit - 168851, // Miniature Flying Carpet (never implemented) - 169669, // Hexweave Cloth (dupe for max results?) - 173415, // WoD ?? - 176313, // [A] Inspiring Battle Standard (garrison building craft) - 176314, // [A] Fearsome Battle Standard (garrison building craft) - 176315, // [H] Inspiring Battle Standard (garrison building craft) - 176316, // [H] Fearsome Battle Standard (garrison building craft) - 1228435, // Mid ?? - 1228998, // Mid ?? - 1228999, // Mid ?? - 1230770, // Mid ?? - 1279126, // Mid ?? - - // Tailoring ranks? - 3908, - 3909, - 3910, - 12180, - 26790, - 51309, - 75156, - 110426, - 158758, - 195126 - ]; } diff --git a/apps/tool/Models/Manual/RedisManualData.cs b/apps/tool/Models/Manual/RedisManualData.cs index 0469a306d..916d566ff 100644 --- a/apps/tool/Models/Manual/RedisManualData.cs +++ b/apps/tool/Models/Manual/RedisManualData.cs @@ -13,16 +13,15 @@ namespace Wowthing.Tool.Models.Manual; public class ManualCache { - public List?> RawCustomizationCategories { get; set; } public List Dragonriding { get; set; } + public List IgnoredSkillLineAbilitySpells { get; set; } + public List> ProgressSets { get; set; } + + public List?> RawCustomizationCategories { get; set; } public DataHeirloomGroup[] RawHeirloomGroups { get; set; } public DataIllusionGroup[] RawIllusionGroups { get; set; } public List> RawMountSets { get; set; } public List> RawPetSets { get; set; } - - //[JsonProperty("rawProgressSets")] - public List> ProgressSets { get; set; } - public List RawReputationSets { get; set; } public List> RawToySets { get; set; } public List> RawTransmogSets { get; set; } diff --git a/apps/tool/Tools/DumpsTool.cs b/apps/tool/Tools/DumpsTool.cs index 5c4a5e658..763109170 100644 --- a/apps/tool/Tools/DumpsTool.cs +++ b/apps/tool/Tools/DumpsTool.cs @@ -1692,7 +1692,7 @@ private async Task ImportRecipeItems(WowDbContext context) foreach (var skillLineAbility in skillLineAbilities) { if (skillLineAbility.Spell == 0 || - Hardcoded.IgnoredSkillLineAbilities.Contains(skillLineAbility.ID) || + // Hardcoded.IgnoredSkillLineAbilities.Contains(skillLineAbility.ID) || !_teachSpellBySpellId.TryGetValue(skillLineAbility.Spell, out var recipeItemIds)) { continue; @@ -1717,9 +1717,9 @@ private async Task ImportRecipeItems(WowDbContext context) } } - await context.WowProfessionRecipeItem - .Where(item => Hardcoded.IgnoredSkillLineAbilities.Contains(item.SkillLineAbilityId)) - .ExecuteDeleteAsync(); + // await context.WowProfessionRecipeItem + // .Where(item => Hardcoded.IgnoredSkillLineAbilities.Contains(item.SkillLineAbilityId)) + // .ExecuteDeleteAsync(); _timer.AddPoint("RecipeItems"); } diff --git a/apps/tool/Tools/ManualTool.cs b/apps/tool/Tools/ManualTool.cs index 440ab58c8..216d3e407 100644 --- a/apps/tool/Tools/ManualTool.cs +++ b/apps/tool/Tools/ManualTool.cs @@ -212,6 +212,9 @@ private async Task CacheData(WowDbContext context) cacheData.RawHeirloomGroups = LoadHeirlooms(); _timer.AddPoint("Heirlooms"); + cacheData.IgnoredSkillLineAbilitySpells = LoadIgnoredSkillLineAbilitySpells(); + _timer.AddPoint("IgnoredSkillLineAbilitySpells"); + cacheData.RawIllusionGroups = LoadIllusions(); _timer.AddPoint("Illusions"); @@ -686,6 +689,24 @@ private DataHeirloomGroup[] LoadHeirlooms() ); } + private List LoadIgnoredSkillLineAbilitySpells() + { + var di = new DirectoryInfo(Path.Join(DataUtilities.DataPath, "professions", "ignored-spells")); + var files = di.GetFiles("*.yml", SearchOption.AllDirectories) + .OrderBy(file => file.FullName) + .ToArray(); + + var ret = new HashSet(); + + foreach (var file in files) + { + int[] spellIds = DataUtilities.YamlDeserializer.Deserialize(File.OpenText(file.FullName)); + ret.UnionWith(spellIds); + } + + return ret.Order().ToList(); + } + private DataIllusionGroup[] LoadIllusions() { return DataUtilities.YamlDeserializer diff --git a/apps/tool/Tools/StaticTool.cs b/apps/tool/Tools/StaticTool.cs index 0b2f91513..1a3a93da7 100644 --- a/apps/tool/Tools/StaticTool.cs +++ b/apps/tool/Tools/StaticTool.cs @@ -762,8 +762,8 @@ private async Task>> LoadProfessions( var skillLineAbilities = await DataUtilities.LoadDumpCsvAsync( "skilllineability", - Language.enUS, - ability => !Hardcoded.IgnoredSkillLineAbilities.Contains(ability.ID) + Language.enUS + // ability => !Hardcoded.IgnoredSkillLineAbilities.Contains(ability.ID) ); var professions = skillLines @@ -909,8 +909,8 @@ private async Task>> LoadProfessions( var abilities = categoryAbilities .GetValueOrDefault(category.ID, []) - .Where(ability => ability.SupercedesSpell == 0 && - !Hardcoded.IgnoredSkillLineAbilitySpells.Contains(ability.Spell)) + .Where(ability => ability.SupercedesSpell == 0)// && + // !Hardcoded.IgnoredSkillLineAbilitySpells.Contains(ability.Spell)) .OrderByDescending(ability => ability.MinSkillLineRank) //.ThenByDescending(ability => ability.TrivialSkillLineRankLow) .ThenByDescending(ability => ability.TrivialSkillLineRankHigh) diff --git a/packages/csharp-lib/Repositories/JobRepository.cs b/packages/csharp-lib/Repositories/JobRepository.cs index d694d4bd7..43a4a2831 100644 --- a/packages/csharp-lib/Repositories/JobRepository.cs +++ b/packages/csharp-lib/Repositories/JobRepository.cs @@ -1,4 +1,5 @@ -using StackExchange.Redis; +using System.Text; +using StackExchange.Redis; using Wowthing.Lib.Contexts; using Wowthing.Lib.Enums; using Wowthing.Lib.Jobs; @@ -24,17 +25,46 @@ INSERT INTO queued_job (priority, type, data, data_hash) ON CONFLICT DO NOTHING"); } - public async Task AddJobsAsync(JobPriority priority, JobType type, IEnumerable datas) + public async Task AddJobsAsync(JobPriority priority, JobType type, List jobParams) { - foreach (var data in datas) + await using var context = await contextFactory.CreateDbContextAsync(); + + // EF Core still doesn't have any way to handle insert conflicts, build a raw SQL string + // https://github.com/dotnet/efcore/issues/16949 + var builder = new StringBuilder("INSERT INTO queued_job (priority, type, data, data_hash) VALUES "); + var parameters = new List + { + priority, + type + }; + + for (int i = 0; i < jobParams.Count; i++) { - await AddJobAsync(priority, type, data); + string[] data = jobParams[i]; + + string json = JsonSerializer.Serialize(data.EmptyIfNull(), jsonSerializerOptions); + string jsonHash = json.Sha256(); + parameters.Add(json); + parameters.Add(jsonHash); + + // 0 => 2, 1 => 4, etc + int paramIndex = (i + 1) * 2; + builder.Append($"(@p0, @p1, @p{paramIndex}, @p{paramIndex + 1})"); + + if (i < jobParams.Count - 1) + { + builder.Append(", "); + } } + + builder.Append(" ON CONFLICT DO NOTHING"); + + await context.Database.ExecuteSqlRawAsync(builder.ToString(), parameters); } - public async Task AddJobsAsync(JobPriority priority, JobType type, IEnumerable datas) + public async Task AddJobsAsync(JobPriority priority, JobType type, List jobParams) { - await AddJobsAsync(priority, type, datas.Select(d => new[] { d })); + await AddJobsAsync(priority, type, jobParams.Select(d => new[] { d }).ToList()); } public async Task AddImageJobAsync(ImageType imageType, int id, ImageFormat format, string url) @@ -70,6 +100,7 @@ public async Task CheckLastTime(string prefix, string suffix, TimeSpan max { await db.StringSetAsync(key, DateTimeOffset.Now.ToString("O"), maximumAge); } + return set; }