diff --git a/src/App.svelte b/src/App.svelte index 4059189c..0cd62ff9 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -503,6 +503,7 @@ Anyway?`, {t("Achievements")} / {t("Conducts")} +
  • {t("Skills")}
  • {t("Proficiencies")}
  • diff --git a/src/types/Skill.svelte b/src/types/Skill.svelte index b3b31209..b7bfb819 100644 --- a/src/types/Skill.svelte +++ b/src/types/Skill.svelte @@ -48,6 +48,102 @@ const itemsUsingSkill = data ) as SupportedTypesWithMapped["GUN"][]; itemsUsingSkill.sort(byName); +const craftingRecipes = data + .byType("recipe") + .filter( + (r) => + r.skill_used === item.id && + r.result && + data.byIdMaybe("item", r.result) && + !r.never_learn + ); + +const recipesByLevel = new Map(); +for (const recipe of craftingRecipes) { + const level = recipe.difficulty ?? 0; + if (!recipesByLevel.has(level)) recipesByLevel.set(level, []); + recipesByLevel.get(level)!.push(recipe); +} +const recipesByLevelList = [...recipesByLevel.entries()].sort( + (a, b) => a[0] - b[0] +); +recipesByLevelList.forEach(([, recipes]) => { + recipes.sort((a, b) => { + const itemA = data.byId("item", a.result!); + const itemB = data.byId("item", b.result!); + if (!itemA || !itemB) return 0; // If either item doesn't exist, consider them equal + return singularName(itemA).localeCompare(singularName(itemB)); + }); +}); + +function getRecipeLearningInfo(recipe: any, recipeLevel: number): string { + const parts = []; + + // Check for autolearn + if (recipe.autolearn) { + if (Array.isArray(recipe.autolearn)) { + // Multiple skills for autolearn - check if it's just current skill at current level + if ( + recipe.autolearn.length === 1 && + recipe.autolearn[0][0] === item.id && + recipe.autolearn[0][1] === recipeLevel + ) { + parts.push("Autolearn"); + } else { + const autolearns = recipe.autolearn.map( + ([skill, level]: [string, number]) => { + const skillData = data.byIdMaybe("skill", skill); + const skillName = skillData ? singularName(skillData) : skill; + return `${skillName} ${level}`; + } + ); + parts.push(`Autolearn ${autolearns.join(", ")}`); + } + } else { + // Single skill autolearn based on recipe difficulty + if (recipe.skill_used) { + const recipeDifficulty = recipe.difficulty ?? 0; + // If autolearn skill matches current skill and level matches the group level, just say "Autolearn" + if (recipe.skill_used === item.id && recipeDifficulty === recipeLevel) { + parts.push("Autolearn"); + } else { + const skillData = data.byIdMaybe("skill", recipe.skill_used); + const skillName = skillData + ? singularName(skillData) + : recipe.skill_used; + parts.push(`Autolearn ${skillName} ${recipeDifficulty}`); + } + } + } + } + + // Check for book learning + const writtenIn = Array.isArray(recipe.book_learn) + ? [...recipe.book_learn] + : [...Object.entries((recipe.book_learn ?? {}) as Record)].map( + ([k, v]) => [k, v.skill_level ?? v] + ); + + if (writtenIn.length > 0) { + if (writtenIn.length >= 3) { + parts.push(`Written in ${writtenIn.length} books`); + } else { + const bookNames = writtenIn.map(([bookId, level]) => { + const book = data.byIdMaybe("item", bookId); + const bookName = book ? singularName(book) : bookId; + return level ? `${bookName} (${level})` : bookName; + }); + if (writtenIn.length === 2) { + parts.push(`Written in ${bookNames.join(" and ")}`); + } else { + parts.push(`Written in ${bookNames[0]}`); + } + } + } + + return parts.length > 0 ? ` (${parts.join("; ")})` : ""; +} + const practiceRecipes = data .byType("practice") .filter((r) => r.skill_used === item.id); @@ -91,6 +187,26 @@ practiceRecipes.sort( {/if} +{#if craftingRecipes.length} +
    +

    {t("Crafting Recipes", { _context: "Skill" })}

    +
    + {#each recipesByLevelList as [level, recipes]} +
    Level {level}
    +
    + + {#if item.result} + {getRecipeLearningInfo(item, level)} + {/if} + +
    + {/each} +
    +
    +{/if} + {#if practiceRecipes.length}

    {t("Practice Recipes", { _context: "Skill" })}

    {#each practiceRecipes as recipe}