diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json
index c35a9ad01..28b2214d8 100644
--- a/apps/frontend/package-lock.json
+++ b/apps/frontend/package-lock.json
@@ -15,6 +15,7 @@
"chart.js": "^4.5.1",
"chartjs-adapter-luxon": "^1.3.1",
"luxon": "^3.7.2",
+ "minisearch": "^7.2.0",
"tippy.js": "^6.3.7"
},
"devDependencies": {
@@ -2926,6 +2927,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/minisearch": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz",
+ "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==",
+ "license": "MIT"
+ },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 8388ab5df..5c5945d75 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -20,6 +20,7 @@
"chart.js": "^4.5.1",
"chartjs-adapter-luxon": "^1.3.1",
"luxon": "^3.7.2",
+ "minisearch": "^7.2.0",
"tippy.js": "^6.3.7"
},
"devDependencies": {
diff --git a/apps/frontend/scss/global.scss b/apps/frontend/scss/global.scss
index b82c3d580..77da7a51b 100644
--- a/apps/frontend/scss/global.scss
+++ b/apps/frontend/scss/global.scss
@@ -10,6 +10,7 @@
@import "error";
@import "forms";
@import "hacks";
+@import "images";
@import "landing-page";
@import "options";
@import "reputation";
diff --git a/apps/frontend/scss/images.scss b/apps/frontend/scss/images.scss
new file mode 100644
index 000000000..720fda4a6
--- /dev/null
+++ b/apps/frontend/scss/images.scss
@@ -0,0 +1,13 @@
+.crafting-quality-2,
+.crafting-quality-13 {
+ scale: 1.3;
+}
+.crafting-quality-4 {
+ scale: 0.8;
+}
+.crafting-quality-5 {
+ scale: 0.7;
+}
+.crafting-quality-14 {
+ scale: 1.1;
+}
diff --git a/apps/frontend/shared/components/images/CraftedQualityIcon.svelte b/apps/frontend/shared/components/images/CraftedQualityIcon.svelte
index 9a338bff8..c761024a8 100644
--- a/apps/frontend/shared/components/images/CraftedQualityIcon.svelte
+++ b/apps/frontend/shared/components/images/CraftedQualityIcon.svelte
@@ -1,29 +1,33 @@
{#if quality > 0}
-
{/if}
diff --git a/apps/frontend/shared/components/images/RaceIcon.svelte b/apps/frontend/shared/components/images/RaceIcon.svelte
index a5697c796..17af86d53 100644
--- a/apps/frontend/shared/components/images/RaceIcon.svelte
+++ b/apps/frontend/shared/components/images/RaceIcon.svelte
@@ -27,9 +27,10 @@
let race = $derived(
characterRace || wowthingData.static.characterRaceById.get(character?.raceId || raceId)
);
+ let derivedGender = $derived(character?.gender || gender || 0);
let tooltip = $derived(
- `${Gender[character?.gender || gender]} ${getGenderedName(race?.name ?? 'Unknown', character?.gender ?? 0)}`
+ `${Gender[derivedGender]} ${getGenderedName(race?.name ?? 'Unknown', derivedGender)}`
);
-
+
diff --git a/apps/frontend/shared/components/images/sources/WowthingImage.svelte b/apps/frontend/shared/components/images/sources/WowthingImage.svelte
index e47cf07e3..c3b2684e3 100644
--- a/apps/frontend/shared/components/images/sources/WowthingImage.svelte
+++ b/apps/frontend/shared/components/images/sources/WowthingImage.svelte
@@ -1,26 +1,23 @@
();
public professionAbilityByItemId = new Map();
public professionAbilityBySpellId = new Map();
+ public professionAbilityPreviousRank = new Map();
public professionBySkillLineId = new Map();
public professionBySlug = new Map();
public requiredProfessionByItemId = new Map();
@@ -238,6 +239,7 @@ export class DataStatic {
)
);
+ let previousAbility: [number, number] = [ability.id, ability.spellId];
for (const [extraAbilityId, extraSpellId] of ability.extraRanks || []) {
const extraItemId = spellToItem[extraSpellId];
if (extraItemId) {
@@ -251,6 +253,9 @@ export class DataStatic {
extraSpellId
)
);
+
+ this.professionAbilityPreviousRank.set(extraAbilityId, previousAbility);
+ previousAbility = [extraAbilityId, extraSpellId];
}
}
}
@@ -262,6 +267,11 @@ export class DataStatic {
for (const itemId of abililtyInfo.itemIds || []) {
this.professionAbilityByItemId.set(itemId, abililtyInfo);
}
+
+ for (const [extraAbilityId, extraSpellId] of abililtyInfo.ability?.extraRanks || []) {
+ this.professionAbilityByAbilityId.set(extraAbilityId, abililtyInfo);
+ this.professionAbilityBySpellId.set(extraSpellId, abililtyInfo);
+ }
}
for (const childCategory of category?.children || []) {
diff --git a/apps/frontend/types/character-race.ts b/apps/frontend/types/character-race.ts
deleted file mode 100644
index ba66d0c6b..000000000
--- a/apps/frontend/types/character-race.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export class CharacterRace {
- id: number;
- name: string;
- // female, male
- icons: string[];
-
- constructor(id: number, name: string) {
- this.id = id;
- this.name = name;
-
- const safeName = name.toLowerCase().replace(/ /g, '_').replace(/'/g, '');
- this.icons = [`race_${safeName}_female`, `race_${safeName}_male`];
- }
-}
diff --git a/apps/frontend/types/character/character.svelte.ts b/apps/frontend/types/character/character.svelte.ts
index 813a5b49d..0152a0d01 100644
--- a/apps/frontend/types/character/character.svelte.ts
+++ b/apps/frontend/types/character/character.svelte.ts
@@ -673,6 +673,15 @@ export class Character implements ContainsItems, HasNameAndRealm {
for (const profession of Object.values(this.professions)) {
for (const abilityId of profession.knownRecipes) {
allKnown.add(abilityId);
+
+ let previousAbility: [number, number];
+ while (
+ (previousAbility = wowthingData.static.professionAbilityPreviousRank.get(
+ previousAbility?.[0] || abilityId
+ ))
+ ) {
+ allKnown.add(previousAbility[0]);
+ }
}
}
return allKnown;
diff --git a/apps/frontend/types/character/profession.svelte.ts b/apps/frontend/types/character/profession.svelte.ts
index 4bfe518a8..dadce23be 100644
--- a/apps/frontend/types/character/profession.svelte.ts
+++ b/apps/frontend/types/character/profession.svelte.ts
@@ -72,6 +72,7 @@ export class CharacterProfession {
syncSet(subProfession.knownRecipes, subKnownRecipes);
}
+
syncSet(this.knownRecipes, allKnownRecipes);
}
diff --git a/apps/frontend/types/index.ts b/apps/frontend/types/index.ts
index 4e281e036..1ead901c3 100644
--- a/apps/frontend/types/index.ts
+++ b/apps/frontend/types/index.ts
@@ -1,7 +1,6 @@
export * from './account';
export * from './achievement-data';
export * from './background-image';
-export * from './character-race';
export * from './covenant';
export * from './difficulty-data';
export * from './dungeon';
diff --git a/apps/frontend/types/props.ts b/apps/frontend/types/props.ts
index c3f93def8..6ad27608a 100644
--- a/apps/frontend/types/props.ts
+++ b/apps/frontend/types/props.ts
@@ -30,3 +30,10 @@ export type SortableProps = {
getSortState: (prefix?: string) => number;
setSortState: (prefix?: string) => void;
};
+
+export type ImageProps = {
+ size: number;
+ border?: number;
+ cls?: string;
+ tooltip?: string;
+};
diff --git a/apps/frontend/types/utility.ts b/apps/frontend/types/utility.ts
new file mode 100644
index 000000000..488df36a5
--- /dev/null
+++ b/apps/frontend/types/utility.ts
@@ -0,0 +1 @@
+export type MakeOptional = Omit & Partial>;
diff --git a/apps/frontend/utils/get-progress.ts b/apps/frontend/utils/get-progress.ts
index 1b73cb728..16832c222 100644
--- a/apps/frontend/utils/get-progress.ts
+++ b/apps/frontend/utils/get-progress.ts
@@ -3,6 +3,7 @@ import find from 'lodash/find';
import { covenantFeatureOrder, covenantMap } from '@/data/covenant';
import { factionIdMap } from '@/data/faction';
import { garrisonBuildingIcon, garrisonTrees, garrisonUnlockQuests } from '@/data/garrison';
+import { GarrisonType } from '@/enums/garrison-type';
import { ProgressDataType } from '@/enums/progress-data-type';
import { QuestStatus } from '@/enums/quest-status';
import { wowthingData } from '@/shared/stores/data';
@@ -56,7 +57,7 @@ export default function getProgress(
let skipRequiredQuests = false;
if (category.name === 'Garrisons' && group.name === 'Pet Quest') {
- const garrison = character.garrisons?.[2];
+ const garrison = character.garrisons?.[GarrisonType.WarlordsOfDraenor];
if (garrison?.level === 3) {
skipRequiredQuests = true;
}
@@ -479,7 +480,8 @@ export default function getProgress(
}
case ProgressDataType.WodGarrison: {
- const garrison = character.garrisons?.[2];
+ const garrison = character.garrisons?.[GarrisonType.WarlordsOfDraenor];
+ if (character.name === 'Momokan') console.log(garrison);
// Garrison level
if (data.value === undefined) {
@@ -490,6 +492,7 @@ export default function getProgress(
if (
checkCharacterQuestIds(character.id, garrisonUnlockQuests)
) {
+ have = 1;
total = 3;
} else {
have = -1;
diff --git a/apps/web/Controllers/ApiController.cs b/apps/web/Controllers/ApiController.cs
index 96332411d..53fc96f9c 100644
--- a/apps/web/Controllers/ApiController.cs
+++ b/apps/web/Controllers/ApiController.cs
@@ -208,6 +208,7 @@ await _context.Users
timer.AddPoint("Accounts");
+ // Warbank
PlayerWarbankItem[] warbankItems = [];
if (!apiResult.Public)
{
@@ -218,23 +219,7 @@ await _context.Users
timer.AddPoint("Warbank");
- var guilds = await _context.PlayerGuild
- .Where(pg => pg.UserId == apiResult.User.Id)
- .ToArrayAsync();
-
- Dictionary guildItemsMap = new();
- if (!apiResult.Public)
- {
- int[] guildIds = guilds.Select(g => g.Id).ToArray();
- var guildItems = await _context.PlayerGuildItem
- .Where(pgi => guildIds.Contains(pgi.GuildId))
- .ToArrayAsync();
- guildItemsMap = guildItems
- .ToGroupedDictionary(pgi => pgi.GuildId);
- }
-
- timer.AddPoint("Guilds");
-
+ // Characters
var characterQuery = _context.PlayerCharacter
.Where(c => c.Account.UserId == apiResult.User.Id)
.Where(c => c.ClassId > 0 && c.RaceId > 0 && c.Level > 0);
@@ -284,8 +269,32 @@ await _context.Users
.Select(character => character.Id)
.ToArray();
+ var guildIds = characters
+ .Where(character => character.GuildId != null)
+ .Select(character => character.GuildId)
+ .Distinct()
+ .ToArray();
+
timer.AddPoint("Characters");
+ // Guilds
+ var guilds = await _context.PlayerGuild
+ .Where(pg => pg.UserId == apiResult.User.Id &&
+ guildIds.Contains(pg.Id))
+ .ToArrayAsync();
+
+ Dictionary guildItemsMap = new();
+ if (!apiResult.Public)
+ {
+ var guildItems = await _context.PlayerGuildItem
+ .Where(pgi => guildIds.Contains(pgi.GuildId))
+ .ToArrayAsync();
+ guildItemsMap = guildItems
+ .ToGroupedDictionary(pgi => pgi.GuildId);
+ }
+
+ timer.AddPoint("Guilds");
+
var itemQuery = _context.PlayerCharacterItem
.AsNoTracking()
.Where(pci => characterIds.Contains(pci.CharacterId));