diff --git a/main/src/shortcuts/HostClipboard.ts b/main/src/shortcuts/HostClipboard.ts index 5fd9ffce..ede12513 100644 --- a/main/src/shortcuts/HostClipboard.ts +++ b/main/src/shortcuts/HostClipboard.ts @@ -99,52 +99,66 @@ export class HostClipboard { } function isPoeItem(text: string) { - return LANGUAGE_DETECTOR.find(({ firstLine }) => text.startsWith(firstLine)); + return LANGUAGE_DETECTOR.find( + ({ firstLine, uncutSkillGemLine }) => + text.startsWith(firstLine) || text.startsWith(uncutSkillGemLine), + ); } const LANGUAGE_DETECTOR = [ { lang: "en", firstLine: "Item Class: ", + uncutSkillGemLine: "Rarity: ", }, { lang: "ru", firstLine: "Класс предмета: ", + uncutSkillGemLine: "Редкость: ", }, { lang: "fr", firstLine: "Classe d'objet: ", + uncutSkillGemLine: "Rareté: ", }, { lang: "de", firstLine: "Gegenstandsklasse: ", + uncutSkillGemLine: "Seltenheit: ", }, { lang: "pt", firstLine: "Classe do Item: ", + uncutSkillGemLine: "Raridade: ", }, { lang: "es", firstLine: "Clase de objeto: ", + uncutSkillGemLine: "Rareza: ", }, { lang: "th", firstLine: "ชนิดไอเทม: ", + uncutSkillGemLine: "Rarity: ", }, { lang: "ko", firstLine: "아이템 종류: ", + uncutSkillGemLine: "아이템 희귀도: ", }, { lang: "cmn-Hant", firstLine: "物品種類: ", + uncutSkillGemLine: "稀有度: ", }, { lang: "cmn-Hans", firstLine: "物品类别: ", + uncutSkillGemLine: "Rarity: ", }, { lang: "ja", firstLine: "アイテムクラス: ", + uncutSkillGemLine: "レアリティ: ", }, ]; diff --git a/renderer/specs/Parser/items.ts b/renderer/specs/Parser/items.ts index 30bd2449..d7cbcba0 100644 --- a/renderer/specs/Parser/items.ts +++ b/renderer/specs/Parser/items.ts @@ -390,6 +390,48 @@ UncutSupportGem.info = { UncutSupportGem.sectionCount = 3; // #endregion UncutSupportGem +// #region MetaSkillGem +export const MetaSkillGem = new TestItem(`Rarity: Gem +Mirage Archer +-------- +Buff, Persistent, Trigger, Duration, Meta +Level: 14 +Reservation: 60 Spirit +-------- +Requires: Level 58, 103 Dex +Requires: Spear, Bow, Crossbow +-------- +Sockets: G G G G +-------- +While active, dodge rolling will create a Mirage that uses socketed ranged Attacks for a short duration, then vanish. +-------- +Support +-------- +Mirages deal 30% less Damage +Socketed Skills cannot consume Charges +-------- +Mirage +-------- +Cooldown Time: 10.00s +-------- +Mirage duration is 5.7 seconds +-------- +Place one or more Skill Gems into this Meta Gem's sockets in the Skills Panel. The socketed Skills will be incorporated into the Meta Gem's effect. +`); +MetaSkillGem.category = ItemCategory.Gem; +MetaSkillGem.gemLevel = 14; +MetaSkillGem.info = { + name: "Mirage Archer", + refName: "Mirage Archer", + namespace: "GEM", + icon: "test", + tags: [], + craftable: { category: ItemCategory.Gem }, +}; + +MetaSkillGem.sectionCount = 11; +// #endregion MetaSkillGem + // #region HighDamageRareItem export const HighDamageRareItem = new TestItem(`Item Class: Crossbows Rarity: Rare diff --git a/renderer/specs/Parser/uncutSkillGem.test.ts b/renderer/specs/Parser/skillGem.test.ts similarity index 69% rename from renderer/specs/Parser/uncutSkillGem.test.ts rename to renderer/specs/Parser/skillGem.test.ts index 1556a8ac..c29a39f3 100644 --- a/renderer/specs/Parser/uncutSkillGem.test.ts +++ b/renderer/specs/Parser/skillGem.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { setupTests } from "@specs/vitest.setup"; import { __testExports, parseClipboard } from "@/parser/Parser"; import { + MetaSkillGem, RareItem, UncutSkillGem, UncutSpiritGem, @@ -10,26 +11,19 @@ import { import { init } from "@/assets/data"; import { createFilters } from "@/web/price-check/filters/create-item-filters"; -describe("isUncutSkillGem", () => { +describe("isItemMissingItemClass", () => { beforeEach(async () => { setupTests(); await init("en"); }); - it("FIXED so should now be false for uncut skill gem", () => { - const sections = __testExports.itemTextToSections(UncutSkillGem.rawText); - expect(__testExports.isUncutSkillGem(sections[0])).toBeFalsy(); - }); - it("FIXED so should now be false for uncut spirit gem", () => { - const sections = __testExports.itemTextToSections(UncutSpiritGem.rawText); - expect(__testExports.isUncutSkillGem(sections[0])).toBeFalsy(); - }); - it("FIXED so should now be false for uncut skill gem", () => { - const sections = __testExports.itemTextToSections(UncutSupportGem.rawText); - expect(__testExports.isUncutSkillGem(sections[0])).toBeFalsy(); + it("so should now be false for meta skill gem", () => { + const sections = __testExports.itemTextToSections(MetaSkillGem.rawText); + expect(__testExports.isItemMissingItemClass(sections[0])).toBeTruthy(); }); + it("should return false for any other item", () => { const sections = __testExports.itemTextToSections(RareItem.rawText); - expect(__testExports.isUncutSkillGem(sections[0])).toBe(false); + expect(__testExports.isItemMissingItemClass(sections[0])).toBe(false); }); }); @@ -56,6 +50,26 @@ describe("Uncut gems parse correctly", () => { expect(result!.category).toBe(UncutSupportGem.category); expect(result!.gemLevel).toBeUndefined(); }); + it("should parse meta skill gem", () => { + const result = parseClipboard(MetaSkillGem.rawText).unwrapOr(null); + expect(result).not.toBeNull(); + expect(result!.category).toBe(MetaSkillGem.category); + expect(result!.gemLevel).toBe(MetaSkillGem.gemLevel); + }); +}); + +describe("Skill gems parse correctly", () => { + beforeEach(async () => { + setupTests(); + await init("en"); + }); + + it("should parse meta skill gem", () => { + const result = parseClipboard(MetaSkillGem.rawText).unwrapOr(null); + expect(result).not.toBeNull(); + expect(result!.category).toBe(MetaSkillGem.category); + expect(result!.gemLevel).toBe(MetaSkillGem.gemLevel); + }); }); describe("Create Filter for uncut gems", () => { diff --git a/renderer/src/assets/data/index.ts b/renderer/src/assets/data/index.ts index 79c08305..2e73a4e4 100644 --- a/renderer/src/assets/data/index.ts +++ b/renderer/src/assets/data/index.ts @@ -10,7 +10,7 @@ import type { } from "./interfaces"; import { loadClientStrings } from "../client-string-loader"; import { useTradeData } from "@/web/background/TradeData"; -import { ItemCategory } from "@/parser/meta"; +import { GEM, ItemCategory } from "@/parser/meta"; import { ItemRarity } from "@/parser/ParsedItem"; export * from "./interfaces"; @@ -404,7 +404,7 @@ async function loadTradeData() { let base: BaseType | undefined; const { baseType, name, rarity, category } = itemQuery; - if (category === ItemCategory.Gem) { + if (category && GEM.has(category)) { if (name && items.has(name)) { base = { name: name, diff --git a/renderer/src/parser/Parser.ts b/renderer/src/parser/Parser.ts index 1a8e8864..c43947ad 100644 --- a/renderer/src/parser/Parser.ts +++ b/renderer/src/parser/Parser.ts @@ -14,7 +14,7 @@ import { tryParseTranslation, getRollOrMinmaxAvg, } from "./stat-translations"; -import { ItemCategory } from "./meta"; +import { GEM, ItemCategory } from "./meta"; import { IncursionRoom, ParsedItem, @@ -232,7 +232,7 @@ function findInDatabase(item: ParserState) { info = ITEM_BY_REF("DIVINATION_CARD", item.name); } else if (item.category === ItemCategory.CapturedBeast) { info = ITEM_BY_REF("CAPTURED_BEAST", item.baseType ?? item.name); - } else if (item.category === ItemCategory.Gem) { + } else if (item.category && GEM.has(item.category)) { info = ITEM_BY_REF("GEM", item.name); } else if (item.category === ItemCategory.MetamorphSample) { info = ITEM_BY_REF("ITEM", item.name); @@ -258,7 +258,7 @@ function findInDatabase(item: ParserState) { info = ITEM_BY_TRANSLATED("DIVINATION_CARD", item.name); } else if (item.category === ItemCategory.CapturedBeast) { info = ITEM_BY_TRANSLATED("CAPTURED_BEAST", item.baseType ?? item.name); - } else if (item.category === ItemCategory.Gem) { + } else if (item.category && GEM.has(item.category)) { info = ITEM_BY_TRANSLATED("GEM", item.name); } else if (item.category === ItemCategory.MetamorphSample) { info = ITEM_BY_TRANSLATED("ITEM", item.name); @@ -448,8 +448,16 @@ function pickCorrectVariant(item: ParserState) { function parseNamePlate(section: string[]) { performance.mark("parseNamePlate"); let line = section.shift(); + + let missingItemClass = false; + if (!line?.startsWith(_$.ITEM_CLASS)) { - return err("item.parse_error"); + // HACK: Meta skill gems + if (line && section.unshift(line) && isItemMissingItemClass(section)) { + missingItemClass = true; + } else { + return err("item.parse_error"); + } } line = section.shift(); @@ -510,6 +518,10 @@ function parseNamePlate(section: string[]) { break; } + if (missingItemClass) { + item.category = ItemCategory.Gem; + } + return ok(item); } @@ -612,6 +624,10 @@ function parseRequirements(section: string[], item: ParsedItem) { return "SECTION_SKIPPED"; } + if (item.category && GEM.has(item.category)) { + return "SECTION_SKIPPED"; + } + const match = section[0].match(_$.REQUIRES_LINE); // TODO: remove once validated in other langs if (!match) { @@ -669,12 +685,16 @@ function parseGem(section: string[], item: ParsedItem) { performance.mark("parseGem"); if ( item.category !== ItemCategory.Gem && + item.category !== ItemCategory.MetaGem && item.category !== ItemCategory.UncutGem ) { return "PARSER_SKIPPED"; } - const gemLevelLineNumber = item.category === ItemCategory.Gem ? 1 : 0; + const gemLevelLineNumber = + item.category === ItemCategory.Gem || item.category === ItemCategory.MetaGem + ? 1 + : 0; if (section[gemLevelLineNumber]?.startsWith(_$.GEM_LEVEL)) { // "Level: 20 (Max)" @@ -756,7 +776,11 @@ function parseAugmentSockets(section: string[], item: ParsedItem) { function parseSockets(section: string[], item: ParsedItem) { performance.mark("parseSockets"); - if (item.category === ItemCategory.Gem && section[0].startsWith(_$.SOCKETS)) { + if ( + item.category && + GEM.has(item.category) && + section[0].startsWith(_$.SOCKETS) + ) { let sockets = section[0].slice(_$.SOCKETS.length).trimEnd(); sockets = sockets.replace(/[^ -]/g, "#"); @@ -1887,10 +1911,9 @@ export function replaceHashWithValues(template: string, values: number[]) { return result; } -function isUncutSkillGem(section: string[]): boolean { - if (section.length !== 2) return false; - const translated = _$.RARITY + _$.RARITY_CURRENCY; - return section[0] === translated && section[1] !== undefined; +function isItemMissingItemClass(section: string[]): boolean { + if (section.length > 3 || section.length < 2) return false; + return section[0].startsWith(_$.RARITY) && section[1] !== undefined; } // Disable since this is export for tests @@ -1899,7 +1922,7 @@ export const __testExports = { itemTextToSections, findInDatabase, parseNamePlate, - isUncutSkillGem, + isItemMissingItemClass, parseWeapon, parseArmour, parseModifiers, diff --git a/renderer/src/parser/meta.ts b/renderer/src/parser/meta.ts index 557f86f6..de85b395 100644 --- a/renderer/src/parser/meta.ts +++ b/renderer/src/parser/meta.ts @@ -123,6 +123,13 @@ export const ACCESSORY = new Set([ // ItemCategory.Quiver ]); +export const GEM = new Set([ + ItemCategory.Gem, + ItemCategory.MetaGem, + ItemCategory.SupportGem, + // ItemCategory.UncutGem, +]); + export enum ItemEditorType { Augment = "augment", Catalyst = "catalyst", diff --git a/renderer/src/web/price-check/filters/create-item-filters.ts b/renderer/src/web/price-check/filters/create-item-filters.ts index 8916cf92..22116d63 100644 --- a/renderer/src/web/price-check/filters/create-item-filters.ts +++ b/renderer/src/web/price-check/filters/create-item-filters.ts @@ -6,7 +6,7 @@ import { BaseType, ITEM_BY_REF } from "@/assets/data"; import { CATEGORY_TO_TRADE_ID } from "../trade/pathofexile-trade"; import { PriceCheckWidget } from "@/web/overlay/widgets"; import { isArmourOrWeaponOrCaster } from "@/parser/Parser"; -import { ARMOUR, WEAPON } from "@/parser/meta"; +import { ARMOUR, GEM, WEAPON } from "@/parser/meta"; import { maxUsefulItemLevel } from "./common"; export const SPECIAL_SUPPORT_GEM = [ @@ -57,7 +57,7 @@ export function createFilters( }, }; - if (item.category === ItemCategory.Gem && !tradeTag(item)) { + if (item.category && GEM.has(item.category) && !tradeTag(item)) { return createGemFilters(item, filters, opts); } if (item.category === ItemCategory.UncutGem) { diff --git a/renderer/src/web/price-check/trade/TradeItem.vue b/renderer/src/web/price-check/trade/TradeItem.vue index 23579f21..c212f6c9 100644 --- a/renderer/src/web/price-check/trade/TradeItem.vue +++ b/renderer/src/web/price-check/trade/TradeItem.vue @@ -34,24 +34,16 @@ {{ result.itemLevel }}