fix meta gems

This commit is contained in:
kvan7
2026-06-11 16:53:39 -05:00
parent eb3a2ac4b8
commit 14bbebc42c
10 changed files with 146 additions and 55 deletions
+15 -1
View File
@@ -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: "レアリティ: ",
},
];
+42
View File
@@ -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
@@ -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", () => {
+2 -2
View File
@@ -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,
+34 -11
View File
@@ -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,
+7
View File
@@ -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",
@@ -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) {
@@ -34,24 +34,16 @@
{{ result.itemLevel }}
</td>
<td
v-if="
item.category === ItemCategory.Gem ||
item.category === ItemCategory.UncutGem
"
v-if="isGem || item.category === ItemCategory.UncutGem"
class="pl-2 whitespace-nowrap"
>
{{ result.level }}
</td>
<td
v-if="item.category === ItemCategory.Gem"
class="pl-2 whitespace-nowrap"
>
<td v-if="isGem" class="pl-2 whitespace-nowrap">
{{ result.gemSockets }}
</td>
<td
v-if="
(quality && !quality.disabled) || item.category === ItemCategory.Gem
"
v-if="(quality && !quality.disabled) || isGem"
class="px-2 whitespace-nowrap text-blue-400 text-right"
>
{{ result.quality }}
@@ -130,6 +122,7 @@ import "tippy.js/dist/tippy.css";
import { AppConfig } from "@/web/Config";
import { ItemCategory } from "@/parser";
import TooltipItem from "./TooltipItem.vue";
import { GEM } from "@/parser/meta";
export default defineComponent({
name: "TradeItem",
@@ -161,7 +154,7 @@ export default defineComponent({
},
},
setup() {
setup(props) {
const tooltipOption = computed(
() => AppConfig<PriceCheckWidget>("price-check")!.itemHoverTooltip,
);
@@ -229,6 +222,9 @@ export default defineComponent({
isHovered,
isShiftPressed,
ItemCategory,
isGem: computed(
() => props.item.category && GEM.has(props.item.category),
),
};
},
});
@@ -30,25 +30,16 @@
<div class="px-2">{{ t(":item_level") }}</div>
</th>
<th
v-if="
item.category === ItemCategory.Gem ||
item.category === ItemCategory.UncutGem
"
v-if="isGem || item.category === ItemCategory.UncutGem"
class="trade-table-heading"
>
<div class="px-2">{{ t(":gem_level") }}</div>
</th>
<th
v-if="item.category === ItemCategory.Gem"
class="trade-table-heading"
>
<th v-if="isGem" class="trade-table-heading">
<div class="px-2">{{ t(":gem_sockets") }}</div>
</th>
<th
v-if="
(filters.quality && !filters.quality.disabled) ||
item.category === ItemCategory.Gem
"
v-if="(filters.quality && !filters.quality.disabled) || isGem"
class="trade-table-heading"
>
<div class="px-2">{{ t(":quality") }}</div>
@@ -139,6 +130,7 @@ import OnlineFilter from "./OnlineFilter.vue";
import TradeLinks from "./TradeLinks.vue";
import TradeItem from "./TradeItem.vue";
import { useTradeApi } from "./trade-api";
import { GEM } from "@/parser/meta";
const slowdown = artificialSlowdown(900);
@@ -245,6 +237,9 @@ export default defineComponent({
// Shift key state and methods
isShiftPressed,
ItemCategory,
isGem: computed(
() => props.item.category && GEM.has(props.item.category),
),
isLikelyPriceFixed: computed(() => {
// if it isn't filling listings it probably is fine
if (groupedResults.value.length <= 15) {
@@ -1,6 +1,6 @@
import { ParsedItem, ItemRarity, ItemCategory } from "@/parser";
import { floorToBracket } from "../filters/create-item-filters";
import { ACCESSORY, ARMOUR, WEAPON } from "@/parser/meta";
import { ACCESSORY, ARMOUR, GEM, WEAPON } from "@/parser/meta";
import { TRADE_TAG_TO_REF } from "@/assets/data";
export function getCurrencyDetailsId(id: string) {
@@ -36,7 +36,7 @@ export function isValuableBasetype(item: ParsedItem): boolean {
}
export function getDetailsId(item: ParsedItem) {
if (item.category === ItemCategory.Gem) {
if (item.category && GEM.has(item.category)) {
return forSkillGem(item);
}
if (item.category === ItemCategory.Map) {