change Id generation to avoid regressions, #501

This commit is contained in:
Alexander Drozdov
2021-12-25 11:24:02 +02:00
parent 9c9640aa4b
commit cc4abd1472
6 changed files with 683 additions and 718 deletions
+559 -559
View File
File diff suppressed because it is too large Load Diff
+2 -14
View File
@@ -1,11 +1,10 @@
import fnv1a from '@bensjoberg/fnv1a'
import type { BaseType, BlightRecipes, DropEntry, Stat, StatMatcher, TranslationDict } from './interfaces'
import { AppConfig } from '@/web/Config'
import { nameToDetailsId } from '@/web/price-check/trends/getDetailsId'
export * from './interfaces'
export const ITEM_DROP = new Map<string, DropEntry>()
export let ITEM_DROP: DropEntry[]
export let BLIGHT_RECIPES: BlightRecipes
export let CLIENT_STRINGS: TranslationDict
@@ -136,17 +135,6 @@ export function stat (text: string) {
// eslint-disable-next-line no-eval
CLIENT_STRINGS = (await eval(`import('/data/${language}/client_strings.js')`)).default
BLIGHT_RECIPES = await (await fetch('/data/blight-recipes.json')).json()
}
{
const itemDrop: DropEntry[] = await (await fetch('/data/item-drop.json')).json()
for (const entry of itemDrop) {
for (const query of entry.query) {
ITEM_DROP.set(nameToDetailsId(query), {
items: entry.items.map(nameToDetailsId),
query: entry.query.map(nameToDetailsId)
})
}
}
ITEM_DROP = await (await fetch('/data/item-drop.json')).json()
}
})()
+42 -38
View File
@@ -1,7 +1,6 @@
import { ref, watch } from 'vue'
import { MainProcess } from '@/ipc/main-process-bindings'
import { selected as selectedLeague, isPublic as isPublicLeague } from './Leagues'
import { nameToDetailsId } from '../price-check/trends/getDetailsId'
interface NinjaCurrencyInfo { /* eslint-disable camelcase */
currencyTypeName: string
@@ -68,36 +67,45 @@ const UPDATE_TIME = 10 * 60 * 1000
export const chaosExaRate = ref<number | undefined>(undefined)
const priceQueue = [
{ overview: 'currency', type: 'Currency', loaded: 0 },
{ overview: 'currency', type: 'Fragment', loaded: 0 },
{ overview: 'item', type: 'Watchstone', loaded: 0 },
{ overview: 'item', type: 'Oil', loaded: 0 },
{ overview: 'item', type: 'Incubator', loaded: 0 },
{ overview: 'item', type: 'Scarab', loaded: 0 },
{ overview: 'item', type: 'Fossil', loaded: 0 },
{ overview: 'item', type: 'Resonator', loaded: 0 },
{ overview: 'item', type: 'Essence', loaded: 0 },
{ overview: 'item', type: 'DivinationCard', loaded: 0 },
{ overview: 'item', type: 'Prophecy', loaded: 0 },
{ overview: 'item', type: 'SkillGem', loaded: 0 },
{ overview: 'item', type: 'BaseType', loaded: 0 },
// { overview: 'item', type: 'HelmetEnchant', loaded: 0 },
{ overview: 'item', type: 'BlightedMap', loaded: 0 },
{ overview: 'item', type: 'BlightRavagedMap', loaded: 0 },
{ overview: 'item', type: 'UniqueMap', loaded: 0 },
{ overview: 'item', type: 'Map', loaded: 0 },
{ overview: 'item', type: 'UniqueJewel', loaded: 0 },
{ overview: 'item', type: 'UniqueFlask', loaded: 0 },
{ overview: 'item', type: 'UniqueWeapon', loaded: 0 },
{ overview: 'item', type: 'UniqueArmour', loaded: 0 },
{ overview: 'item', type: 'UniqueAccessory', loaded: 0 },
{ overview: 'item', type: 'Beast', loaded: 0 },
{ overview: 'item', type: 'Vial', loaded: 0 },
{ overview: 'item', type: 'DeliriumOrb', loaded: 0 },
{ overview: 'item', type: 'Invitation', loaded: 0 },
{ overview: 'item', type: 'Artifact', loaded: 0 }
]
const priceQueue = (() => {
function uniqueItemKey (item: NinjaItemInfo, extra = '') {
let key = `UNIQUE::${item.name} // ${item.baseType}`
if (item.variant) key += ` // ${item.variant}`
if (extra) key += ` // ${extra}`
return key
}
return [
{ overview: 'currency', type: 'Currency', loaded: 0 },
{ overview: 'currency', type: 'Fragment', loaded: 0 },
{ overview: 'item', type: 'Watchstone', loaded: 0, key: (item: NinjaItemInfo) => `ITEM::${item.name} // ${item.variant}` },
{ overview: 'item', type: 'Oil', loaded: 0 },
{ overview: 'item', type: 'Incubator', loaded: 0 },
{ overview: 'item', type: 'Scarab', loaded: 0 },
{ overview: 'item', type: 'Fossil', loaded: 0 },
{ overview: 'item', type: 'Resonator', loaded: 0 },
{ overview: 'item', type: 'Essence', loaded: 0 },
{ overview: 'item', type: 'DivinationCard', loaded: 0, key: (item: NinjaItemInfo) => `DIVINATION_CARD::${item.name}` },
{ overview: 'item', type: 'Prophecy', loaded: 0, key: (item: NinjaItemInfo) => `PROPHECY::${item.name}` + ((item.variant) ? ` // ${item.variant}` : '') },
{ overview: 'item', type: 'SkillGem', loaded: 0, key: (item: NinjaItemInfo) => `GEM::${item.name} // ${item.gemLevel} // ${item.gemQuality ?? 0}%` + ((item.corrupted) ? ' // Corrupted' : '') },
{ overview: 'item', type: 'BaseType', loaded: 0, key: (item: NinjaItemInfo) => `ITEM::${item.name} // ${item.levelRequired}` + ((item.variant) ? ` // ${item.variant}` : '') },
// { overview: 'item', type: 'HelmetEnchant', loaded: 0 },
{ overview: 'item', type: 'BlightedMap', loaded: 0, key: (item: NinjaItemInfo) => `ITEM::${item.name} // T${item.mapTier}` },
{ overview: 'item', type: 'BlightRavagedMap', loaded: 0, key: (item: NinjaItemInfo) => `ITEM::${item.name} // T${item.mapTier}` },
{ overview: 'item', type: 'UniqueMap', loaded: 0, key: (item: NinjaItemInfo) => `UNIQUE::${item.name} // T${item.mapTier}` },
{ overview: 'item', type: 'Map', loaded: 0, key: (item: NinjaItemInfo) => `ITEM::${item.name} // T${item.mapTier}` },
{ overview: 'item', type: 'UniqueJewel', loaded: 0, key: uniqueItemKey },
{ overview: 'item', type: 'UniqueFlask', loaded: 0, key: uniqueItemKey },
{ overview: 'item', type: 'UniqueWeapon', loaded: 0, key: (item: NinjaItemInfo) => uniqueItemKey(item, (item.links) ? `${item.links}L` : '') },
{ overview: 'item', type: 'UniqueArmour', loaded: 0, key: (item: NinjaItemInfo) => uniqueItemKey(item, (item.links) ? `${item.links}L` : '') },
{ overview: 'item', type: 'UniqueAccessory', loaded: 0, key: uniqueItemKey },
{ overview: 'item', type: 'Beast', loaded: 0, key: (item: NinjaItemInfo) => `CAPTURED_BEAST::${item.name}` },
{ overview: 'item', type: 'Vial', loaded: 0 },
{ overview: 'item', type: 'DeliriumOrb', loaded: 0 },
{ overview: 'item', type: 'Invitation', loaded: 0 },
{ overview: 'item', type: 'Artifact', loaded: 0 }
]
})()
async function load (force: boolean = false) {
if (!selectedLeague.value || !isPublicLeague.value) return
@@ -126,7 +134,7 @@ async function load (force: boolean = false) {
continue
}
PRICE_BY_DETAILS_ID.set(currency.detailsId, {
PRICE_BY_DETAILS_ID.set(`ITEM::${currency.currencyTypeName}`, {
detailsId: currency.detailsId,
icon: priceData.currencyDetails.find(detail => detail.id === currency.receive!.get_currency_id)!.icon,
name: currency.currencyTypeName,
@@ -156,12 +164,8 @@ async function load (force: boolean = false) {
lines: NinjaItemInfo[]
} = await response.json()
for (const item of priceData.lines) {
const detailsId = dataType.type === 'UniqueFlask' // seems poe.ninja keeps this for compatability
? nameToDetailsId(`${item.detailsId} ${item.baseType}`)
: item.detailsId
PRICE_BY_DETAILS_ID.set(detailsId, {
detailsId,
PRICE_BY_DETAILS_ID.set(dataType.key?.(item) ?? `ITEM::${item.name}`, {
detailsId: item.detailsId,
icon: item.icon,
name: item.name,
chaosValue: item.chaosValue,
@@ -10,7 +10,6 @@
import { defineComponent, PropType, computed } from 'vue'
import { StatFilter } from './interfaces'
import { autoCurrency, displayRounding, findByDetailsId, ItemInfo } from '@/web/background/Prices'
import { nameToDetailsId } from '../trends/getDetailsId'
import { BLIGHT_RECIPES } from '@/assets/data'
export default defineComponent({
@@ -30,8 +29,7 @@ export default defineComponent({
if (!oils.length) return null
const prices = oils
.map(nameToDetailsId)
.map(findByDetailsId)
.map(oil => findByDetailsId(`ITEM::${oil}`))
.filter(Boolean) as ItemInfo[]
if (prices.length !== oils.length) return null
@@ -50,9 +50,11 @@ export default defineComponent({
})
const result = computed(() => {
if (!detailsId.value || !ITEM_DROP.has(detailsId.value)) return null
if (!detailsId.value) return null
const r = ITEM_DROP.find(entry => entry.query.includes(detailsId.value!))
if (!r) return null
const r = ITEM_DROP.get(detailsId.value)!
const out = {
// TODO: show at least icon when price is not available on poe.ninja yet
related: r.query.map(id => findByDetailsId(id)).filter(_ => Boolean(_)),
+75 -102
View File
@@ -16,112 +16,92 @@ export function isValuableBasetype (item: ParsedItem): boolean {
)
}
const LATEST_MAP_VARIANT = 'Gen-12'
export function getDetailsId (item: ParsedItem) {
const ns = item.info.namespace
if (item.category === ItemCategory.Gem) {
return getGemDetailsId(item)!
return forSkillGem(item)
}
if (item.category === ItemCategory.Map) {
if (item.rarity === ItemRarity.Unique) {
return nameToDetailsId(`${item.info.refName} t${item.mapTier}`)
return `${ns}::${item.info.refName} // T${item.mapTier}`
} else {
return nameToDetailsId(`${item.mapBlighted ? `${item.mapBlighted} ` : ''}${item.info.refName} t${item.mapTier} ${LATEST_MAP_VARIANT}`)
return `${ns}::${item.mapBlighted ? `${item.mapBlighted} ` : ''}${item.info.refName} // T${item.mapTier}`
}
}
if (item.category === ItemCategory.CapturedBeast ||
item.category === ItemCategory.MavenInvitation) {
return nameToDetailsId(item.info.refName)
}
if (item.rarity === ItemRarity.Unique) {
return getUniqueDetailsId(item)
return forUniqueItem(item)
}
if (isValuableBasetype(item)) {
return getBaseTypeDetailsId(item)
return forBasetype(item)
}
if (item.info.prophecy?.masterName) {
return nameToDetailsId(`${item.info.refName} ${item.info.prophecy.masterName}`)
return `${ns}::${item.info.refName} // ${item.info.prophecy.masterName}`
}
if (item.category === ItemCategory.Seed) {
return nameToDetailsId(`${item.info.refName} ${item.itemLevel! >= 76 ? '76' : '1-75'}`)
}
if (item.category === ItemCategory.DivinationCard && item.info.refName === 'The Scout') {
return 'the-scout-divcard'
}
if (item.category === ItemCategory.Prophecy && item.info.refName === 'The Twins') {
return 'the-twins-prophecy'
}
return nameToDetailsId(item.info.name)
return `${ns}::${item.info.name}`
}
const BRAND_RECALL_GEM = 'Brand Recall'
const BLOOD_AND_SAND_GEM = 'Blood and Sand'
const PORTAL_GEM = 'Portal'
function getGemDetailsId (item: ParsedItem) {
if (item.info.refName === PORTAL_GEM) {
return 'portal-1'
}
function forSkillGem (item: ParsedItem) {
let id = item.gemAltQuality === 'Superior'
? nameToDetailsId(item.info.refName)
: nameToDetailsId(`${item.gemAltQuality} ${item.info.refName}`)
? `${item.info.namespace}::${item.info.refName}`
: `${item.info.namespace}::${item.gemAltQuality} ${item.info.refName}`
if (
SPECIAL_SUPPORT_GEM.includes(item.info.refName) ||
item.info.refName === BRAND_RECALL_GEM ||
item.info.refName === BLOOD_AND_SAND_GEM ||
item.info.refName === 'Portal' ||
item.info.refName === 'Brand Recall' ||
item.info.refName === 'Blood and Sand' ||
item.gemLevel! >= 20
) {
id += `-${item.gemLevel}`
id += ` // ${item.gemLevel}`
} else {
id += '-1'
id += ' // 1'
}
if (item.quality) {
if (
!SPECIAL_SUPPORT_GEM.includes(item.info.refName) &&
!(item.info.refName === BRAND_RECALL_GEM && item.isCorrupted)
// @TODO(poe.ninja blocking): !(item.info.refName === BLOOD_AND_SAND_GEM && item.isCorrupted)
) {
// Gem Q20 with up to 4xGCP (TODO: should this rule apply to corrupted gems?)
const q = (item.quality >= 16 && item.quality <= 20) ? 20 : item.quality
id += `-${q}`
}
if (
item.quality &&
!SPECIAL_SUPPORT_GEM.includes(item.info.refName) &&
!(item.info.refName === 'Brand Recall' && item.isCorrupted)
// @TODO(poe.ninja blocking): !(item.info.refName === 'Blood and Sand' && item.isCorrupted)
) {
// Gem Q20 with up to 4xGCP (TODO: should this rule apply to corrupted gems?)
const q = (item.quality >= 16 && item.quality <= 20) ? 20 : item.quality
id += ` // ${q}%`
} else {
id += ' // 0%'
}
if (item.isCorrupted) {
id += 'c'
if (item.isCorrupted && item.info.refName !== 'Portal') {
id += ' // Corrupted'
}
return id
}
function getBaseTypeDetailsId (item: ParsedItem) {
let id = nameToDetailsId(item.info.refName)
function forBasetype (item: ParsedItem) {
let id = `${item.info.namespace}::${item.info.refName}`
id += `-${Math.min(item.itemLevel!, 86)}`
id += ` // ${Math.min(item.itemLevel!, 86)}`
if (item.influences.length === 1) {
id += `-${item.influences[0].toLowerCase()}`
id += ` // ${item.influences[0]}`
return id
} else if (item.influences.length > 1) {
return undefined
}
return id
}
function getUniqueDetailsId (item: ParsedItem) {
function forUniqueItem (item: ParsedItem) {
if (!item.info.unique) return
let id = nameToDetailsId(`${item.info.refName}${getUniqueVariant(item) || ''} ${item.info.unique.base}`)
let id = `${item.info.namespace}::${item.info.refName} // ${item.info.unique.base}`
const variant = getUniqueVariant(item)
if (variant) {
id += ` // ${variant}`
}
if (item.sockets?.linked) {
id += `-${item.sockets.linked}l`
id += ` // ${item.sockets.linked}L`
}
if (item.info.unique.base === 'Ivory Watchstone') {
const uses = item.statsByType.find(m => m.type === 'explicit' && m.stat.ref === '# uses remaining')!
const roll = uses.sources[0].contributes!.value
id += `-${roll}`
id += ` // ${roll}`
}
return id
@@ -136,96 +116,89 @@ function getUniqueVariant (item: ParsedItem) {
if (uniqueName === 'Vessel of Vinktar') {
if (hasStat(item, 'Adds # to # Lightning Damage to Attacks during Flask effect')) {
return '-added-attacks'
return 'Added Attacks'
} else if (hasStat(item, 'Adds # to # Lightning Damage to Spells during Flask effect')) {
return '-added-spells'
return 'Added Spells'
} else if (hasStat(item, 'Damage Penetrates #% Lightning Resistance during Flask effect')) {
return '-penetration'
return 'Penetration'
} else if (hasStat(item, '#% of Physical Damage Converted to Lightning during Flask effect')) {
return '-conversion'
return 'Conversion'
}
} else if (uniqueName === "Atziri's Splendour") {
if (hasStat(item, '#% increased Armour, Evasion and Energy Shield')) {
return '-armour-evasion-es'
return 'Armour/Evasion/ES'
} else if (hasStat(item, '#% increased Evasion and Energy Shield') && hasStat(item, '+# to maximum Energy Shield')) {
return '-evasion-es'
return 'Evasion/ES'
} else if (hasStat(item, '#% increased Evasion and Energy Shield') && hasStat(item, '+# to maximum Life')) {
return '-evasion-es-life'
return 'Evasion/ES/Life'
} else if (hasStat(item, '#% increased Armour and Energy Shield') && hasStat(item, '+# to maximum Energy Shield')) {
return '-armour-es'
return 'Armour/ES'
} else if (hasStat(item, '#% increased Armour and Energy Shield') && hasStat(item, '+# to maximum Life')) {
return '-armour-es-life'
return 'Armour/ES/Life'
} else if (hasStat(item, '#% increased Armour and Evasion') && hasStat(item, '+# to maximum Life')) {
return '-armour-evasion'
return 'Armour/Evasion'
} else if (hasStat(item, '+# to maximum Energy Shield') && hasStat(item, '#% increased Energy Shield')) {
return '-es'
return 'ES'
} else if (hasStat(item, '#% increased Evasion Rating') && hasStat(item, '+# to maximum Life')) {
return '-evasion'
return 'Evasion'
} else if (hasStat(item, '#% increased Armour') && hasStat(item, '+# to maximum Life')) {
return '-armour'
return 'Armour'
}
} else if (uniqueName === 'Bubonic Trail' || uniqueName === 'Lightpoacher' || uniqueName === 'Shroud of the Lightless' || uniqueName === 'Tombfist') {
const sockets = item.statsByType.find(m => m.type === 'explicit' && m.stat.ref === 'Has # Abyssal Sockets')!
const roll = sockets.sources[0].contributes!.value
if (roll === 2) {
return '-2-jewels'
return '2 Jewels'
} else if (roll === 1) {
return '-1-jewel'
return '1 Jewel'
}
} else if (uniqueName === "Volkuur's Guidance") {
if (hasStat(item, 'Adds # to # Cold Damage to Spells and Attacks')) {
return '-cold'
return 'Cold'
} else if (hasStat(item, 'Adds # to # Fire Damage to Spells and Attacks')) {
return '-fire'
return 'Fire'
} else if (hasStat(item, 'Adds # to # Lightning Damage to Spells and Attacks')) {
return '-lightning'
return 'Lightning'
}
} else if (uniqueName === "Yriel's Fostering") {
if (hasStat(item, 'Projectiles from Attacks have #% chance to Maim on Hit while\nyou have a Bestial Minion')) {
return '-maim'
return 'Maim'
} else if (hasStat(item, 'Projectiles from Attacks have #% chance to Poison on Hit while\nyou have a Bestial Minion')) {
return '-poison'
return 'Poison'
} else if (hasStat(item, 'Projectiles from Attacks have #% chance to inflict Bleeding on Hit while\nyou have a Bestial Minion')) {
return '-bleeding'
return 'Bleeding'
}
} else if (uniqueName === "Doryani's Invitation") {
if (hasStat(item, '#% increased Global Physical Damage')) {
return '-physical'
return 'Physical'
} else if (hasStat(item, '#% increased Fire Damage')) {
return '-fire'
return 'Fire'
} else if (hasStat(item, '#% increased Cold Damage')) {
return '-cold'
return 'Cold'
} else if (hasStat(item, '#% increased Lightning Damage')) {
return '-lightning'
return 'Lightning'
}
} else if (uniqueName === 'Impresence') {
if (hasStat(item, 'Adds # to # Cold Damage')) {
return '-cold'
return 'Cold'
} else if (hasStat(item, 'Adds # to # Chaos Damage')) {
return '-chaos'
return 'Chaos'
} else if (hasStat(item, 'Adds # to # Fire Damage')) {
return '-fire'
return 'Fire'
} else if (hasStat(item, 'Adds # to # Lightning Damage')) {
return '-lightning'
return 'Lightning'
} else if (hasStat(item, 'Adds # to # Physical Damage')) {
return '-physical'
return 'Physical'
}
} else if (uniqueName === 'Voices') {
const passives = item.statsByType.find(m => m.stat.ref === 'Adds # Small Passive Skills which grant nothing')!
const roll = passives.sources[0].contributes!.value
if (roll === 7) {
return '-7-passives'
return '7 passives'
} else if (roll === 5) {
return '-5-passives'
return '5 passives'
} else if (roll === 3) {
return '-3-passives'
return '3 passives'
}
}
}
export function nameToDetailsId (name: string) {
return name
.toLowerCase()
.replace(/\s/g, '-')
.replace(/'|,/g, '')
}