diff --git a/src/lib/components/AdditionStat.svelte b/src/lib/components/AdditionStat.svelte index 49db1c09..fdf6bcfc 100644 --- a/src/lib/components/AdditionStat.svelte +++ b/src/lib/components/AdditionStat.svelte @@ -4,6 +4,7 @@ export let text: string; export let data: string; + export let subData: string | undefined = undefined; export let asterisk: boolean = false; let className: string | null | undefined = undefined; @@ -13,7 +14,13 @@
{text}:
- {data} + {data} + {#if subData} + {subData} + {/if} + + {#if asterisk} * {/if} diff --git a/src/lib/layouts/stats/Main.svelte b/src/lib/layouts/stats/Main.svelte index 8ecb0132..8a8ab891 100644 --- a/src/lib/layouts/stats/Main.svelte +++ b/src/lib/layouts/stats/Main.svelte @@ -5,6 +5,7 @@ import Stats from "$lib/layouts/stats/Stats.svelte"; import Accessories from "$lib/sections/stats/Accessories.svelte"; import Armor from "$lib/sections/stats/Armor.svelte"; + import Dungeons from "$lib/sections/stats/Dungeons.svelte"; import Inventory from "$lib/sections/stats/Inventory.svelte"; import Pets from "$lib/sections/stats/Pets.svelte"; import SkillsSection from "$lib/sections/stats/SkillsSection.svelte"; @@ -29,6 +30,7 @@ + diff --git a/src/lib/sections/stats/Dungeons.svelte b/src/lib/sections/stats/Dungeons.svelte new file mode 100644 index 00000000..41880552 --- /dev/null +++ b/src/lib/sections/stats/Dungeons.svelte @@ -0,0 +1,178 @@ + + +
+

Dungeons

+ {#if dungeons} +
+ + {#each Object.entries(dungeons.classes.classes) as [className, classData]} + + {/each} +
+
+ + +
+
+

Total Class XP: {format(dungeons.classes.totalClassExp.toFixed(2))}

+

Total Class XP gained in Catacombs.

+
+
+

Average Level: {format(dungeons.classes.classAverageWithProgress.toFixed(2))}

+

Average class level, includes progress to next level.

+
+
+

Average Level without progress: {format(dungeons.classes.classAverage.toFixed(2))}

+

Average class level without including partial level progress.

+
+
+
+ + + +
+
+

Catacombs

+
+ {#if dungeons.catacombs} + {#each dungeons.catacombs as catacomb} +
+
+ + + + + + + {catacomb.name} +
+ + + + +

Floor Stats

+
+ + {#each Object.entries(catacomb.stats) as [key, value]} + {#if typeof value === "object"} + + {:else} + + {/if} + {/each} + +
+ + {#if catacomb.best_run} + + + +

Best run

+
+ + {#each Object.entries(catacomb.best_run) as [key, value]} + {#if typeof value === "number"} + {#if key === "timestamp"} + + {formatDate(value, "dd MMMM yyyy 'at' HH:mm")} + + {:else} + + {/if} + {:else} + + {/if} + {/each} + +
+ {:else} +
This player has not played this floor.
+ {/if} +
+ {/each} + {:else} + This player has not played any Catacombs. + {/if} +
+
+ +
+

Master Catacombs

+
+ {#if dungeons.master_catacombs} + {#each dungeons.master_catacombs as catacomb} +
+
+ + + + + + + {catacomb.name} +
+ + + + +

Floor Stats

+
+ + {#each Object.entries(catacomb.stats) as [key, value]} + {#if typeof value === "object"} + + {:else} + + {/if} + {/each} + +
+ + {#if catacomb.best_run} + + + +

Best run

+
+ + {#each Object.entries(catacomb.best_run) as [key, value]} + {#if typeof value === "number"} + {#if key === "timestamp"} + + {formatDate(value, "dd MMMM yyyy 'at' HH:mm")} + + {:else} + + {/if} + {:else} + + {/if} + {/each} + +
+ {:else} +
This player has not played this floor.
+ {/if} +
+ {/each} + {:else} + This player has not played any Master Catacombs. + {/if} +
+
+ {/if} +
diff --git a/src/lib/stats/bestiary.ts b/src/lib/stats/bestiary.ts index a53e4143..e8189d4e 100644 --- a/src/lib/stats/bestiary.ts +++ b/src/lib/stats/bestiary.ts @@ -1,5 +1,5 @@ -import type { Member } from "$types/global"; import * as constants from "$constants/constants"; +import type { Member } from "$types/global"; import type { BestiaryStats } from "$types/processed/profile/bestiary"; function getBestiaryMobs( @@ -35,6 +35,24 @@ function getBestiaryMobs( return output; } +export function getBestiaryFamily(userProfile: Member, mobName: string) { + const bestiary = userProfile.bestiary.kills || {}; + const family = Object.values(constants.BESTIARY) + .flatMap((category) => category.mobs) + .find((mob) => mob.name === mobName); + + if (family === undefined) { + return null; + } + + const output = getBestiaryMobs(bestiary, [family]); + if (!output.length) { + return null; + } + + return output[0]; +} + export function getBestiary(userProfile: Member) { const bestiary = userProfile.bestiary.kills || {}; diff --git a/src/lib/stats/dungeons.ts b/src/lib/stats/dungeons.ts index 420ad46f..1a25f53a 100644 --- a/src/lib/stats/dungeons.ts +++ b/src/lib/stats/dungeons.ts @@ -1,6 +1,7 @@ import * as constants from "$constants/constants"; import * as helper from "$lib/helper"; import type { BestRun, Catacombs, Member, Skill } from "$types/global"; +import { getBestiaryFamily } from "./bestiary"; import { getLevelByXp } from "./leveling/leveling"; function getDungeonClasses(userProfile: Member) { @@ -85,6 +86,22 @@ function getBestRun(catacombs: Catacombs, floorId: number) { }; } +function getSecrets(catacombs: Member["dungeons"]) { + const secretsFound = catacombs.secrets ?? 0; + const totalRuns = + Object.keys(catacombs?.dungeon_types?.catacombs?.tier_completions || {}) + .filter((key) => key !== "total") + .reduce((a, b) => a + (catacombs?.dungeon_types?.catacombs?.tier_completions[b] || 0), 0) + + Object.keys(catacombs?.dungeon_types?.master_catacombs?.tier_completions || {}) + .filter((key) => key !== "total") + .reduce((a, b) => a + (catacombs?.dungeon_types?.master_catacombs?.tier_completions[b] || 0), 0); + + return { + found: secretsFound, + secretsPerRun: secretsFound / totalRuns + }; +} + function formatCatacombsData(catacombs: Catacombs) { const type = catacombs.experience ? "catacombs" : "master_catacombs"; @@ -94,22 +111,20 @@ function formatCatacombsData(catacombs: Catacombs) { output.push({ name: floor.name, texture: floor.texture, - - times_played: catacombs.times_played?.[floor.id] ?? 0, - tier_completions: catacombs.tier_completions?.[floor.id] ?? 0, - milestone_completions: catacombs.milestone_completions?.[floor.id] ?? 0, - best_score: catacombs.best_score?.[floor.id] ?? 0, - - mobs_killed: catacombs.mobs_killed?.[floor.id] ?? 0, - watcher_kills: catacombs.watcher_kills?.[floor.id] ?? 0, - most_mobs_killed: catacombs.most_mobs_killed?.[floor.id] ?? 0, - - fastest_time: catacombs.fastest_time?.[floor.id] ?? 0, - fastest_time_s: catacombs.fastest_time?.[floor.id] ?? 0, - fastest_time_s_plus: catacombs.fastest_time_s_plus?.[floor.id] ?? 0, - - most_healing: catacombs.most_healing?.[floor.id] ?? 0, - most_damage: getMostDamage(catacombs, floor.id), + stats: { + times_played: catacombs.times_played?.[floor.id] ?? 0, + tier_completions: catacombs.tier_completions?.[floor.id] ?? 0, + milestone_completions: catacombs.milestone_completions?.[floor.id] ?? 0, + best_score: catacombs.best_score?.[floor.id] ?? 0, + mobs_killed: catacombs.mobs_killed?.[floor.id] ?? 0, + watcher_kills: catacombs.watcher_kills?.[floor.id] ?? 0, + most_mobs_killed: catacombs.most_mobs_killed?.[floor.id] ?? 0, + fastest_time: catacombs.fastest_time?.[floor.id] ?? 0, + fastest_time_s: catacombs.fastest_time?.[floor.id] ?? 0, + fastest_time_s_plus: catacombs.fastest_time_s_plus?.[floor.id] ?? 0, + most_healing: catacombs.most_healing?.[floor.id] ?? 0, + most_damage: getMostDamage(catacombs, floor.id) + }, best_run: getBestRun(catacombs, floor.id) }); } @@ -133,8 +148,11 @@ export function getDungeons(userProfile: Member) { classAverageWithProgress: Object.values(dungeonClasses).reduce((a, b) => a + b.levelWithProgress, 0) / Object.keys(dungeonClasses).length, totalClassExp: Object.values(userProfile.dungeons.player_classes).reduce((a, b) => a + b.experience, 0) }, - secrets: { - found: userProfile.dungeons.secrets + stats: { + secrets: getSecrets(userProfile.dungeons), + highestFloorBeatenNormal: userProfile.dungeons.dungeon_types.catacombs.highest_tier_completed ?? 0, + highestFloorBeatenMaster: userProfile.dungeons.dungeon_types.master_catacombs.highest_tier_completed ?? 0, + bloodMobKills: getBestiaryFamily(userProfile, "Undead")?.kills ?? 0 }, catacombs: formatCatacombsData(userProfile.dungeons.dungeon_types.catacombs), master_catacombs: formatCatacombsData(userProfile.dungeons.dungeon_types.master_catacombs) diff --git a/src/types/processed/profile/dungeons.d.ts b/src/types/processed/profile/dungeons.d.ts index 82b51260..dc71ef88 100644 --- a/src/types/processed/profile/dungeons.d.ts +++ b/src/types/processed/profile/dungeons.d.ts @@ -9,8 +9,14 @@ export type DungeonsStats = { classAverageWithProgress: number; totalClassExp: number; }; - secrets: { - found: number; + stats: { + secrets: { + found: number; + secretsPerRun: number; + }; + highestFloorBeatenNormal: number; + highestFloorBeatenMaster: number; + bloodMobKills: number; }; catacombs: CatacombsData[]; master_catacombs: CatacombsData[]; @@ -19,20 +25,22 @@ export type DungeonsStats = { export type CatacombsData = { name: string; texture: string; - times_played: number; - tier_completions: number; - milestone_completions: number; - best_score: number; - mobs_killed: number; - watcher_kills: number; - most_mobs_killed: number; - fastest_time: number; - fastest_time_s: number; - fastest_time_s_plus: number; - most_healing: number; - most_damage: { - damage: number; - type: string; + stats: { + times_played: number; + tier_completions: number; + milestone_completions: number; + best_score: number; + mobs_killed: number; + watcher_kills: number; + most_mobs_killed: number; + fastest_time: number; + fastest_time_s: number; + fastest_time_s_plus: number; + most_healing: number; + most_damage: { + damage: number; + type: string; + }; }; best_run: { grade: string;