WIP mod search

This commit is contained in:
Alexander Drozdov
2020-01-16 20:08:44 +02:00
parent c68048cfd7
commit 251068ca56
3 changed files with 202 additions and 6 deletions
+79
View File
@@ -0,0 +1,79 @@
import { Mod } from '../src/data'
const fs = require('fs')
const nfetch = require('node-fetch')
interface ApiStats {
result: Array<{
label: string,
entries: Array<{
id: string
text: string
type: string
option?: {
options: Array<{
id: string | number
text: string
}>
}
}>
}>
}
const SUPPORTED_MODS = ['pseudo', 'explicit', 'implicit', 'enchant', 'crafted']
;(async function () {
const response = await nfetch('https://www.pathofexile.com/api/trade/data/stats')
const { result }: ApiStats = await response.json()
const dupes = new Set<string>()
const MOD_BY_NAME = new Map<string, Mod>()
for (const _ of result) {
for (const rawMod of _.entries) {
if (SUPPORTED_MODS.includes(rawMod.type)) {
const mod = MOD_BY_NAME.get(rawMod.text)
if (mod) {
if (mod.types.find(type => type.name === rawMod.type)) {
dupes.add(rawMod.text)
// invalidate mod
mod.types.find(type => type.name === rawMod.type)!.tradeId = undefined
} else {
mod.types.push({ name: rawMod.type, tradeId: rawMod.id })
}
} else {
if (!rawMod.option) {
MOD_BY_NAME.set(rawMod.text, {
text: rawMod.text,
types: [{
name: rawMod.type,
tradeId: rawMod.id
}]
})
} else {
for (const option of rawMod.option.options) {
MOD_BY_NAME.set(rawMod.text.replace('#', option.text), {
text: rawMod.text,
types: [{
name: rawMod.type,
tradeId: rawMod.id
}],
option: {
text: option.text,
tradeId: option.id
}
})
}
}
}
}
}
}
if (dupes.size) {
console.warn('Dupe mods found, consider to report to GGG')
console.warn(Array.from(dupes))
console.warn('Dupe mods found, consider to report to GGG')
}
fs.writeFileSync('./src/data/mods.json', JSON.stringify(Array.from(MOD_BY_NAME), null, 2))
})()
+107 -6
View File
@@ -14,7 +14,7 @@ import {
PREFIX_SUPERIOR,
SUFFIX_INFLUENCE
} from './parser-constants'
import { Prophecies, ItemisedMonsters } from '../data'
import { Prophecies, ItemisedMonsters, Mods, Mod } from '../data'
import { getDetailsId, nameToDetailsId } from './trends/getDetailsId'
import { ItemInfo, Prices } from './Prices'
@@ -37,6 +37,11 @@ export interface ParsedItem {
gemLevel?: number
influences: ItemInfluence[]
rawText: string
modifiers: Array<{
mod: Mod
values?: number[]
type: ModifierType
}>
computed: {
category?: ItemCategory
mapName?: string
@@ -51,6 +56,14 @@ export enum ItemCategory {
ItemisedMonster = 'Itemised Monster'
}
export enum ModifierType {
Pseudo = 'pseudo',
Explicit = 'explicit',
Implicit = 'implicit',
Crafted = 'crafted',
Enchant = 'enchant'
}
const SECTION_PARSED = 1
const SECTION_SKIPPED = 0
const PARSER_SKIPPED = -1
@@ -75,7 +88,10 @@ const parsers: ParserFn[] = [
parseCorrupted,
parseInfluence,
parseMap,
parseSockets
parseSockets,
parseModifiers, // enchant
parseModifiers, // implicit
parseModifiers // explicit
]
const parserAfterHooks = new Map<ParserFn, ParserAfterHookFn>([
@@ -106,6 +122,7 @@ export function parseClipboard (clipboard: string) {
// need to think how to handle it
return null
}
sections.shift()
// each section can be parsed at most by one parser
for (const parser of parsers) {
@@ -185,15 +202,18 @@ function parseNamePlate (section: string[]) {
case ItemRarity.Magic:
case ItemRarity.Rare:
case ItemRarity.Unique:
return {
const item : ParsedItem = {
rarity,
name: section[1].replace(/^(<<.*?>>|<.*?>)+/, ''), // Item from chat "<<set:MS>><<set:M>><<set:S>>Beast Grinder"
baseType: section[2],
isUnidentified: false,
isCorrupted: false,
influences: [] as ItemInfluence[],
computed: {}
} as ParsedItem
modifiers: [],
influences: [],
computed: {},
rawText: undefined!
}
return item
default:
return null
}
@@ -306,6 +326,87 @@ function parseQualityNested (section: string[], item: ParsedItem) {
}
}
function parseModifiers (section: string[], item: ParsedItem) {
const IMPLICIT_SUFFIX = ' (implicit)'
const CRAFTED_SUFFIX = ' (crafted)'
const countBefore = item.modifiers.length
for (let line of section) {
let modType: ModifierType | undefined
let mod: Mod | undefined
const values: number[] = []
// cleanup suffix
if (line.endsWith(IMPLICIT_SUFFIX)) {
line = line.slice(0, -IMPLICIT_SUFFIX.length)
modType = ModifierType.Implicit
} else if (line.endsWith(CRAFTED_SUFFIX)) {
line = line.slice(0, -CRAFTED_SUFFIX.length)
modType = ModifierType.Crafted
}
// 1. try to find "as is"
mod = Mods.get(line)
if (!mod) {
// 2. replace
// example: +#%, #%, #, +#, # to #, #-#
line = line.replace(/(?<![\d])[+-]?[\d.]+/g, (value) => {
values.push(Number(value))
return '#'
})
if (values.length !== 1 && values.length !== 2) {
continue
}
// 3. match variants
// @TODO: IMPORTANT! distinguish between local and global mods
const variants = [
{ invertSign: false, text: line },
{ invertSign: false, text: line + ' (Local)' },
{ invertSign: false, text: line.replace('#', '+#') },
{ invertSign: false, text: line.replace('#', '+#') + ' (Local)' },
{ invertSign: true, text: line.replace('#% reduced', '#% increased') },
{ invertSign: true, text: line.replace('#% reduced', '#% increased') + ' (Local)' }
]
for (const variant of variants) {
mod = Mods.get(variant.text)
if (mod) {
if (variant.invertSign) {
values[0] *= -1
}
break
}
}
}
if (mod) {
if (modType == null) {
for (const type of mod.types) {
if (
type.name !== ModifierType.Pseudo &&
type.name !== ModifierType.Implicit &&
type.name !== ModifierType.Crafted
) {
// explicit/enchant
modType = type.name as ModifierType
}
}
}
item.modifiers.push({
mod,
type: modType!,
values: values.length ? values : undefined
})
}
}
if (countBefore < item.modifiers.length) {
return SECTION_PARSED
}
return SECTION_SKIPPED
}
// --------
function enrichItem (item: ParsedItem) {
+16
View File
@@ -1,5 +1,21 @@
import prophecies from './prophecies.json'
import monsters from './itemised-monsters.json'
import mods from './mods.json'
export interface Mod {
text: string
types: Array<{
name: string
tradeId?: string
// undefined when dupe mod found. Alternative impl is to store
// all tradeIds in array, and apply Logical OR with "min: 1 matched"
}>
option?: {
text: string
tradeId: string | number
}
}
export const Prophecies = new Set(prophecies as string[])
export const ItemisedMonsters = new Set(monsters as string[])
export const Mods = new Map<string, Mod>(mods)