initial commit

This commit is contained in:
Jo Blo
2026-03-04 17:01:08 -05:00
commit 39d61d797d
51 changed files with 7864 additions and 0 deletions

456
src/data/categories.json Normal file
View File

@@ -0,0 +1,456 @@
[
{
"label": "Weapons",
"children": [
{
"label": "Bow",
"children": [
"Bow",
"Longbow",
"Warbow",
"Whispering Bow",
"Wailing Bow",
"Bow of Badon",
"Mistpiercer",
"Skystrider Bow"
]
},
{
"label": "Crossbow",
"children": [
"Crossbow",
"Heavy Crossbow",
"Light Crossbow",
"Weeping Repeater",
"Boltcasters",
"Siegebow",
"Energy Shaper",
"Arclight Blasters"
]
},
{
"label": "Axe",
"children": [
"Battleaxe",
"Greataxe",
"Halberd",
"Carrioncaller",
"Infernal Scythe",
"Bear Paws",
"Realmbreaker",
"Crystal Reaper"
]
},
{
"label": "Dagger",
"children": [
"Dagger",
"Dagger Pair",
"Claws",
"Bloodletter",
"Demon Fang",
"Deathgivers",
"Bridled Fury",
"Twin Slayers"
]
},
{
"label": "Hammer",
"children": [
"Hammer",
"Great Hammer",
"Polehammer",
"Tombhammer",
"Forge Hammers",
"Grovekeeper",
"Hand of Justice",
"Truebolt Hammer"
]
},
{
"label": "War Gloves",
"children": [
"Brawler Gloves",
"Battle Bracers",
"Spiked Gauntlets",
"Ursine Maulers",
"Hellfire Hands",
"Ravenstrike Cestus",
"Fists of Avalon",
"Forcepulse Bracers"
]
},
{
"label": "Mace",
"children": [
"Mace",
"Heavy Mace",
"Morning Star",
"Bedrock Mace",
"Incubus Mace",
"Camlann Mace",
"Oathkeepers",
"Dreadstorm Monarch"
]
},
{
"label": "Quarterstaff",
"children": [
"Quarterstaff",
"Iron-clad Staff",
"Double Bladed Staff",
"Black Monk Stave",
"Staff of Balance",
"Grailseeker",
"Phantom Twinblade"
]
},
{
"label": "Spear",
"children": [
"Spear",
"Pike",
"Glaive",
"Heron Spear",
"Spirithunter",
"Trinity Spear",
"Daybreaker",
"Rift Glaive"
]
},
{
"label": "Sword",
"children": [
"Broadsword",
"Claymore",
"Dual Swords",
"Clarent Blade",
"Carving Sword",
"Galatine Pair",
"Kingmaker",
"Infinity Blade"
]
},
{
"label": "Arcane Staff",
"children": [
"Arcane Staff",
"Great Arcane Staff",
"Enigmatic Staff",
"Witchwork Staff",
"Occult Staff",
"Malevolent Locus",
"Evensong",
"Astral Staff"
]
},
{
"label": "Cursed Staff",
"children": [
"Cursed Staff",
"Great Cursed Staff",
"Demonic Staff",
"Lifecurse Staff",
"Cursed Skull",
"Damnation Staff",
"Shadowcaller",
"Rotcaller Staff"
]
},
{
"label": "Fire Staff",
"children": [
"Fire Staff",
"Great Fire Staff",
"Infernal Staff",
"Wildfire Staff",
"Brimstone Staff",
"Blazing Staff",
"Dawnsong",
"Flamewalker Staff"
]
},
{
"label": "Frost Staff",
"children": [
"Frost Staff",
"Great Frost Staff",
"Glacial Staff",
"Hoarfrost Staff",
"Icicle Staff",
"Permafrost Prism",
"Chillhowl",
"Arctic Staff"
]
},
{
"label": "Holy Staff",
"children": [
"Holy Staff",
"Great Holy Staff",
"Divine Staff",
"Lifetouch Staff",
"Fallen Staff",
"Redemption Staff",
"Hallowfall",
"Exalted Staff"
]
},
{
"label": "Nature Staff",
"children": [
"Nature Staff",
"Great Nature Staff",
"Wild Staff",
"Druidic Staff",
"Blight Staff",
"Rampant Staff",
"Ironroot Staff",
"Forgebark Staff"
]
},
{
"label": "Shapeshifter",
"children": [
"Prowling Staff",
"Rootbound Staff",
"Primal Staff",
"Bloodmoon Staff",
"Hellspawn Staff",
"Earthrune Staff",
"Lightcaller",
"Stillgaze Staff"
]
}
]
},
{
"label": "Armor",
"children": [
{
"label": "Head",
"children": [
{
"label": "Plate",
"children": [
"Soldier Helmet",
"Knight Helmet",
"Guardian Helmet",
"Graveguard Helmet",
"Royal Helmet",
"Demon Helmet",
"Judicator Helmet",
"Duskweaver Helmet",
"Helmet of Valor"
]
},
{
"label": "Leather",
"children": [
"Mercenary Hood",
"Hunter Hood",
"Assassin Hood",
"Stalker Hood",
"Royal Hood",
"Hellion Hood",
"Specter Hood",
"Mistwalker Hood",
"Hood of Tenacity"
]
},
{
"label": "Cloth",
"children": [
"Scholar Cowl",
"Cleric Cowl",
"Mage Cowl",
"Druid Cowl",
"Royal Cowl",
"Fiend Cowl",
"Cultist Cowl",
"Feyscale Cowl",
"Cowl of Purity"
]
}
]
},
{
"label": "Chest",
"children": [
{
"label": "Plate",
"children": [
"Soldier Armor",
"Knight Armor",
"Guardian Armor",
"Graveguard Armor",
"Royal Armor",
"Demon Armor",
"Judicator Armor",
"Duskweaver Armor",
"Armor of Valor"
]
},
{
"label": "Leather",
"children": [
"Mercenary Jacket",
"Hunter Jacket",
"Assassin Jacket",
"Stalker Jacket",
"Royal Jacket",
"Hellion Jacket",
"Specter Jacket",
"Mistwalker Jacket",
"Jacket of Tenacity"
]
},
{
"label": "Cloth",
"children": [
"Scholar Robe",
"Cleric Robe",
"Mage Robe",
"Druid Robe",
"Royal Robe",
"Fiend Robe",
"Cultist Robe",
"Feyscale Robe",
"Robe of Purity"
]
}
]
},
{
"label": "Boots",
"children": [
{
"label": "Plate",
"children": [
"Soldier Boots",
"Knight Boots",
"Guardian Boots",
"Graveguard Boots",
"Royal Boots",
"Demon Boots",
"Judicator Boots",
"Duskweaver Boots",
"Boots of Valor"
]
},
{
"label": "Leather",
"children": [
"Mercenary Shoes",
"Hunter Shoes",
"Assassin Shoes",
"Stalker Shoes",
"Royal Shoes",
"Hellion Shoes",
"Specter Shoes",
"Mistwalker Shoes",
"Shoes of Tenacity"
]
},
{
"label": "Cloth",
"children": [
"Scholar Sandals",
"Cleric Sandals",
"Mage Sandals",
"Druid Sandals",
"Royal Sandals",
"Fiend Sandals",
"Cultist Sandals",
"Feyscale Sandals",
"Sandals of Purity"
]
}
]
}
]
},
{
"label": "Off-hand",
"children": [
{
"label": "Mage",
"children": []
},
{
"label": "Hunter",
"children": []
},
{
"label": "Warrior",
"children": []
}
]
},
{
"label": "Gathering Equipment",
"children": [
{
"label": "Fishing",
"children": [
"Fishing Rods",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
{
"label": "Fiber",
"children": [
"Sickle",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
{
"label": "Hide",
"children": [
"Skinning Knife",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
{
"label": "Ore",
"children": [
"Pickaxe",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
{
"label": "Stone",
"children": [
"Hammer",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
{
"label": "Wood",
"children": [
"Axes",
"Garbs",
"Caps",
"Workboots",
"Backpacks"
]
},
"Tracking Toolkit"
]
}
]

24
src/data/constants.ts Normal file
View File

@@ -0,0 +1,24 @@
import type { AlbionCity, AlbionQuality } from '../types/api'
import type { Tier, ItemCategory, Enchantment } from '../types/crafting'
export const CITIES: AlbionCity[] = [
'Caerleon',
'Bridgewatch',
'Fort Sterling',
'Lymhurst',
'Martlock',
'Thetford',
'Black Market',
]
export const QUALITIES: { label: string; value: AlbionQuality }[] = [
{ label: 'Normal', value: 1 },
{ label: 'Good', value: 2 },
{ label: 'Outstanding', value: 3 },
{ label: 'Excellent', value: 4 },
{ label: 'Masterpiece', value: 5 },
]
export const TIERS: Tier[] = [4, 5, 6, 7, 8]
export const ENCHANTMENTS: Enchantment[] = [0, 1, 2, 3, 4]
export const CATEGORIES: ItemCategory[] = ['Weapons', 'Armor', 'Gathering']

29
src/data/itemTree.ts Normal file
View File

@@ -0,0 +1,29 @@
import rawCategories from './categories.json'
export interface TreeNode {
label: string
children?: TreeNode[]
}
type RawNode = { label: string; children?: (RawNode | string)[] }
function normalize(raw: RawNode): TreeNode {
if (!raw.children) return { label: raw.label }
return {
label: raw.label,
children: raw.children.map(c => typeof c === 'string' ? { label: c } : normalize(c)),
}
}
export function getLeaves(node: TreeNode): string[] {
if (!node.children) return [node.label]
const result: string[] = []
for (const child of node.children) {
result.push(...getLeaves(child))
}
return result
}
export const ITEM_TREE: TreeNode[] = (rawCategories as RawNode[]).map(normalize)
export const ALL_ITEM_NAMES: Set<string> = new Set(ITEM_TREE.flatMap(getLeaves))

2287
src/data/recipes.json Normal file

File diff suppressed because it is too large Load Diff

80
src/data/recipes.ts Normal file
View File

@@ -0,0 +1,80 @@
import type { CraftingRecipe, Tier, Enchantment, ItemCategory, JournalType } from '../types/crafting'
import { ENCHANTMENTS } from './constants'
import rawRecipes from './recipes.json'
// ─── Template types ───────────────────────────────────────────────────────────
interface IngredientTemplate {
itemId: string
qty: number | Record<string, number>
noEnchant?: boolean
}
interface RecipeTemplate {
id: string
displayName: string
category: ItemCategory
outputId: string
enchanted?: boolean
ingredients: IngredientTemplate[]
}
const ALL_TIERS: Tier[] = [4, 5, 6, 7, 8]
// ─── Journal type inference ────────────────────────────────────────────────────
function inferJournalType(outputId: string): JournalType {
if (outputId.includes('_TOOL_')) return 'toolmaker'
if (/FIRESTAFF|INFERNOSTAFF|ARCANESTAFF|ENIGMATICSTAFF|CURSEDSTAFF|DEMONICSTAFF|FROSTSTAFF|GLACIALSTAFF|HOLYSTAFF|DIVINESTAFF|NATURESTAFF|WILDSTAFF|_CLOTH_|OFF_BOOK|ENIGMATICORB|RINGPAIR|SKULLORB|ICEGAUNTLETS|ICECRYSTAL/.test(outputId)) return 'mage'
if (/BOW|_LEATHER_|MAIN_DAGGER|DAGGERPAIR|CLAWPAIR|OFF_TORCH|SHAPESHIFTER|RAPIER|DUALSICKLE|2H_DAGGER/.test(outputId)) return 'hunter'
return 'warrior'
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
function applyTier(template: string, tier: number): string {
return template
.replace(/\{t-1\}/g, String(tier - 1))
.replace(/\{t\}/g, String(tier))
}
function resolveQty(qty: number | Record<string, number>, tier: number): number {
if (typeof qty === 'number') return qty
const val = qty[String(tier)]
if (val === undefined) throw new Error(`No qty defined for tier ${tier}`)
return val
}
// ─── Build all recipes ────────────────────────────────────────────────────────
export const ALL_RECIPES: CraftingRecipe[] = []
for (const entry of rawRecipes) {
if (!('outputId' in entry)) continue // skip comment-only entries
const template = entry as unknown as RecipeTemplate
const enchants: Enchantment[] = template.enchanted === false ? [0] : ENCHANTMENTS
for (const tier of ALL_TIERS) {
for (const enc of enchants) {
const baseOutputId = applyTier(template.outputId, tier)
const outputItemId = enc === 0 ? baseOutputId : `${baseOutputId}@${enc}`
const ingredients = template.ingredients.map(ing => {
const baseId = applyTier(ing.itemId, tier)
const itemId = (ing.noEnchant === true || enc === 0) ? baseId : `${baseId}_LEVEL${enc}`
return { itemId, quantity: resolveQty(ing.qty, tier) }
})
ALL_RECIPES.push({
outputItemId,
displayName: `T${tier}.${enc} ${template.displayName}`,
tier,
enchantment: enc,
category: template.category,
journalType: inferJournalType(template.outputId),
ingredients,
})
}
}
}