initial commit
This commit is contained in:
131
src/composables/useCraftingProfit.ts
Normal file
131
src/composables/useCraftingProfit.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { computed } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import type { CraftingRecipe, ProfitResult, IngredientBreakdown, SortState } from '../types/crafting'
|
||||
import type { FilterState } from '../types/filters'
|
||||
import { useAlbionPrices } from './useAlbionPrices'
|
||||
import { formatItemId } from '../utils/formatting'
|
||||
|
||||
// Returns 0=basic, 1=artifact, 2=avalon, 3=crystal
|
||||
function variantRank(outputItemId: string): number {
|
||||
const id = outputItemId.replace(/@\d$/, '') // strip enchantment suffix
|
||||
if (id.endsWith('_AVALON')) return 2
|
||||
if (id.endsWith('_CRYSTAL')) return 3
|
||||
if (/_SET[123]$/.test(id)) return 0
|
||||
// Artifact suffixes: UNDEAD, HELL, MORGANA, KEEPER, and unique named artifacts
|
||||
if (/_(?:UNDEAD|HELL|MORGANA|KEEPER|ROYAL|FEY)$/.test(id)) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
export function useCraftingProfit(
|
||||
recipes: CraftingRecipe[],
|
||||
filters: Ref<FilterState>,
|
||||
sortState: Ref<SortState>
|
||||
) {
|
||||
const { getPrice } = useAlbionPrices()
|
||||
|
||||
const profitResults = computed<ProfitResult[]>(() => {
|
||||
const f = filters.value
|
||||
const city = f.city
|
||||
const rrrFactor = 1 - f.rrr / 100
|
||||
|
||||
const results: ProfitResult[] = []
|
||||
const nameLower = f.nameFilter.trim().toLowerCase()
|
||||
|
||||
for (const recipe of recipes) {
|
||||
if (!f.tiers.has(recipe.tier)) continue
|
||||
if (f.enchantments !== null && !f.enchantments.has(recipe.enchantment)) continue
|
||||
const baseName = recipe.displayName.replace(/^T\d+\.\d /, '')
|
||||
if (f.selectedItemTypes !== null && !f.selectedItemTypes.has(baseName)) continue
|
||||
if (nameLower && !recipe.displayName.toLowerCase().includes(nameLower)) continue
|
||||
|
||||
// Calculate material cost from ingredients only
|
||||
let materialCost = 0
|
||||
let missingPrices = false
|
||||
|
||||
// Track oldest price date (for status column)
|
||||
let oldestDate: string | null = null
|
||||
function trackDate(date: string) {
|
||||
if (!oldestDate || date < oldestDate) oldestDate = date
|
||||
}
|
||||
|
||||
const ingredientBreakdown: IngredientBreakdown[] = []
|
||||
|
||||
for (const ing of recipe.ingredients) {
|
||||
const ingEntry = getPrice(ing.itemId, city)
|
||||
const unitPrice = ingEntry?.sell_price_min ?? 0
|
||||
if (ingEntry === null || unitPrice === 0) {
|
||||
missingPrices = true
|
||||
ingredientBreakdown.push({
|
||||
itemId: ing.itemId,
|
||||
displayName: formatItemId(ing.itemId),
|
||||
quantity: ing.quantity,
|
||||
unitPrice: 0,
|
||||
totalCost: 0,
|
||||
})
|
||||
} else {
|
||||
trackDate(ingEntry.sell_price_min_date)
|
||||
const totalCost = unitPrice * ing.quantity
|
||||
materialCost += totalCost
|
||||
ingredientBreakdown.push({
|
||||
itemId: ing.itemId,
|
||||
displayName: formatItemId(ing.itemId),
|
||||
quantity: ing.quantity,
|
||||
unitPrice,
|
||||
totalCost,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveMaterialCost = materialCost * rrrFactor
|
||||
const priceAgeMs = missingPrices ? null : (oldestDate ? Date.now() - new Date(oldestDate).getTime() : null)
|
||||
|
||||
results.push({
|
||||
recipe,
|
||||
materialCost,
|
||||
effectiveMaterialCost,
|
||||
priceAgeMs,
|
||||
missingPrices,
|
||||
ingredientBreakdown,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort results
|
||||
const { field, direction } = sortState.value
|
||||
results.sort((a, b) => {
|
||||
let aVal: number | string
|
||||
let bVal: number | string
|
||||
|
||||
switch (field) {
|
||||
case 'materialCost':
|
||||
aVal = a.effectiveMaterialCost
|
||||
bVal = b.effectiveMaterialCost
|
||||
break
|
||||
case 'displayName':
|
||||
aVal = a.recipe.displayName
|
||||
bVal = b.recipe.displayName
|
||||
break
|
||||
case 'tier':
|
||||
aVal = a.recipe.tier
|
||||
bVal = b.recipe.tier
|
||||
break
|
||||
case 'variantType':
|
||||
aVal = variantRank(a.recipe.outputItemId)
|
||||
bVal = variantRank(b.recipe.outputItemId)
|
||||
break
|
||||
default:
|
||||
aVal = a.effectiveMaterialCost
|
||||
bVal = b.effectiveMaterialCost
|
||||
}
|
||||
|
||||
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
||||
return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal)
|
||||
}
|
||||
const diff = (aVal as number) - (bVal as number)
|
||||
return direction === 'asc' ? diff : -diff
|
||||
})
|
||||
|
||||
return results
|
||||
})
|
||||
|
||||
return { profitResults }
|
||||
}
|
||||
Reference in New Issue
Block a user