Compare commits

...

27 Commits

Author SHA1 Message Date
Kvan7
c067f4c17b Merge pull request #801 from Kvan7/dev
v0.13.8
2025-12-22 21:23:30 -06:00
Kvan7
b50584c913 version bump 2025-12-22 21:15:30 -06:00
Kvan7
d9d28c2564 update workflow 2025-12-22 21:05:21 -06:00
Kvan7
8ff70d39ac Disable foil by default (nobody opening relequaries) 2025-12-22 21:02:34 -06:00
Kvan7
51febcd66e Merge pull request #795 from chrisheib/add_level_filter
Add level filter
2025-12-22 21:01:42 -06:00
Kvan7
1fde68ef46 shorten english name 2025-12-22 14:58:09 -06:00
kvan7
c41c2925dc update app_i18n 2025-12-22 20:39:52 -06:00
kvan7
abf3e67c1b Add max level & drop att filters 2025-12-22 20:29:56 -06:00
kvan7
403d9fb4bf localize parsing 2025-12-22 13:31:41 -06:00
Kvan7
211b031633 Merge branch 'dev' into add_level_filter 2025-12-21 19:55:32 -06:00
kvan7
95c3c7cfcd fix fracture detection 2025-12-21 19:52:51 -06:00
STSchiff
538c209e6d more linter happyness (safely access requires.level) 2025-12-21 21:34:30 +01:00
kvan7
c23d9b5eb3 temp detection for fractured items 2025-12-20 20:29:39 -06:00
STSchiff
532a615d0f try to make linter happy 2025-12-20 20:12:06 +01:00
kvan7
73cd5a4712 remove split mac 2025-12-20 13:02:50 -06:00
STSchiff
eb05596dd0 formatter 2025-12-20 19:56:54 +01:00
STSchiff
1928fd47da level filter: renderer & trade query 2025-12-20 19:56:50 +01:00
STSchiff
019ee881ea add requirements parser 2025-12-20 19:56:46 +01:00
Kvan7
e4c347c71b Merge pull request #791 from Kvan7/dev
v0.13.7
2025-12-20 09:49:04 -06:00
kvan7
85b36be1cf [Bug]: Missing app_i18n.json for mod.rune
Fixes #665
2025-12-20 09:32:25 -06:00
kvan7
eaff4d1f68 Map price checking presets
Fixes #729
2025-12-20 09:24:33 -06:00
kvan7
38cff7017d [Bug]: Traditional Chinese elemental resistance affixes not recognized
Fixes #790
2025-12-20 09:22:19 -06:00
kvan7
acb414c62c version bump 2025-12-20 08:50:45 -06:00
kvan7
66b601b8a9 forgot to bump version 2025-12-20 08:42:52 -06:00
kvan7
25c2e2cd5b Remove backspace press when doing stash search
Fixes #694
2025-12-20 08:33:08 -06:00
kvan7
0bb1e91637 Revert to use apt OverlayWindow 2025-12-20 08:32:15 -06:00
kvan7
9cbdc499e3 update drops 2025-12-19 22:45:51 -06:00
46 changed files with 873 additions and 431 deletions

View File

@@ -59,6 +59,8 @@ body:
label: Version label: Version
description: What version of EE2 are you running? You can see this in Settings -> About description: What version of EE2 are you running? You can see this in Settings -> About
options: options:
- 0.13.8
- 0.13.7
- 0.13.6 - 0.13.6
- 0.13.5 - 0.13.5
- 0.13.4 - 0.13.4
@@ -70,7 +72,7 @@ body:
- 0.11.x - 0.11.x
- 0.10.x - 0.10.x
- Change me - Change me
default: 10 default: 12
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@@ -1,4 +1,8 @@
on: on:
push:
branches:
- 'master'
- 'dev'
pull_request: pull_request:
branches: branches:
- master - master

View File

@@ -1,6 +1,6 @@
# ![Perfect Jewelers Orb](./renderer/public/images/jeweler.png) Exiled Exchange 2 # ![Perfect Jewelers Orb](./renderer/public/images/jeweler.png) Exiled Exchange 2
![GitHub Downloads (specific asset, latest release)](https://img.shields.io/github/downloads/kvan7/exiled-exchange-2/latest/Exiled-Exchange-2-Setup-0.13.6.exe?style=plastic&link=https%3A%2F%2Ftooomm.github.io%2Fgithub-release-stats%2F%3Fusername%3Dkvan7%26repository%3DExiled-Exchange-2) ![GitHub Downloads (specific asset, latest release)](https://img.shields.io/github/downloads/kvan7/exiled-exchange-2/latest/Exiled-Exchange-2-Setup-0.13.8.exe?style=plastic&link=https%3A%2F%2Ftooomm.github.io%2Fgithub-release-stats%2F%3Fusername%3Dkvan7%26repository%3DExiled-Exchange-2)
![GitHub Tag](https://img.shields.io/github/v/tag/kvan7/exiled-exchange-2?style=plastic&label=latest%20version) ![GitHub Tag](https://img.shields.io/github/v/tag/kvan7/exiled-exchange-2?style=plastic&label=latest%20version)
![GitHub commits since latest release (branch)](https://img.shields.io/github/commits-since/kvan7/exiled-exchange-2/latest/dev?style=plastic) ![GitHub commits since latest release (branch)](https://img.shields.io/github/commits-since/kvan7/exiled-exchange-2/latest/dev?style=plastic)

View File

@@ -20,7 +20,7 @@ export default defineConfig({
}, },
themeConfig: { themeConfig: {
// logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401 // logo: 'TODO', https://github.com/vuejs/vitepress/issues/1401
appVersion: '0.13.6', appVersion: '0.13.8',
github: { github: {
releasesUrl: 'https://github.com/Kvan7/Exiled-Exchange-2/releases' releasesUrl: 'https://github.com/Kvan7/Exiled-Exchange-2/releases'
}, },

12
main/package-lock.json generated
View File

@@ -1,14 +1,14 @@
{ {
"name": "exiled-exchange-2", "name": "exiled-exchange-2",
"version": "0.13.6", "version": "0.13.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "exiled-exchange-2", "name": "exiled-exchange-2",
"version": "0.13.6", "version": "0.13.8",
"dependencies": { "dependencies": {
"electron-overlay-window": "4.0.1", "electron-overlay-window": "4.0.2",
"uiohook-napi": "1.5.x" "uiohook-napi": "1.5.x"
}, },
"devDependencies": { "devDependencies": {
@@ -3572,9 +3572,9 @@
} }
}, },
"node_modules/electron-overlay-window": { "node_modules/electron-overlay-window": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/electron-overlay-window/-/electron-overlay-window-4.0.1.tgz", "resolved": "https://registry.npmjs.org/electron-overlay-window/-/electron-overlay-window-4.0.2.tgz",
"integrity": "sha512-N+xihP19QaydNHPBz2T+Uv70sj8WUp1xdP/M+4mbjSkZyCsMrLG6r6gIJzS13Ic6eCdyaQE7MFL69z3JwtlF5g==", "integrity": "sha512-HFN/t6k+8/+2QIUQq5bkczEudGAxpsR5GmUbZXbHvHtCZhqHZOMDqPikeP8ZIXQe8wpjTzWo/KUq3eE2hFQwMQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "exiled-exchange-2", "name": "exiled-exchange-2",
"version": "0.13.6", "version": "0.13.8",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node build/script.mjs", "dev": "node build/script.mjs",
@@ -20,7 +20,7 @@
}, },
"main": "dist/main.js", "main": "dist/main.js",
"dependencies": { "dependencies": {
"electron-overlay-window": "4.0.1", "electron-overlay-window": "4.0.2",
"uiohook-napi": "1.5.x" "uiohook-napi": "1.5.x"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -26,9 +26,8 @@ if (process.platform !== "darwin") {
app.enableSandbox(); app.enableSandbox();
let tray: AppTray; let tray: AppTray;
// Ensure accessibility permissions on MacOS.
if (process.platform === "darwin") {
(async () => { (async () => {
if (process.platform === "darwin") {
async function ensureAccessibilityPermission(): Promise<boolean> { async function ensureAccessibilityPermission(): Promise<boolean> {
if (systemPreferences.isTrustedAccessibilityClient(false)) return true; if (systemPreferences.isTrustedAccessibilityClient(false)) return true;
@@ -60,6 +59,8 @@ if (process.platform === "darwin") {
return; return;
} }
console.log("Accessibility permission granted, starting app"); console.log("Accessibility permission granted, starting app");
}
app.on("ready", async () => { app.on("ready", async () => {
tray = new AppTray(eventPipe); tray = new AppTray(eventPipe);
const logger = new Logger(eventPipe); const logger = new Logger(eventPipe);
@@ -86,78 +87,6 @@ if (process.platform === "darwin") {
logger.write(`error [unhandledRejection] ${(reason as Error).stack}`); logger.write(`error [unhandledRejection] ${(reason as Error).stack}`);
}); });
setTimeout(
async () => {
const overlay = new OverlayWindow(eventPipe, logger, poeWindow);
// eslint-disable-next-line no-new
new OverlayVisibility(eventPipe, overlay, gameConfig);
const shortcuts = await Shortcuts.create(
logger,
overlay,
poeWindow,
gameConfig,
eventPipe,
);
eventPipe.onEventAnyClient(
"CLIENT->MAIN::update-host-config",
(cfg) => {
overlay.updateOpts(cfg.overlayKey, cfg.windowTitle);
shortcuts.updateActions(
cfg.shortcuts,
cfg.stashScroll,
cfg.logKeys,
cfg.restoreClipboard,
cfg.language,
);
gameLogWatcher.restart(cfg.clientLog ?? "", cfg.readClientLog);
gameConfig.readConfig(cfg.gameConfig ?? "");
appUpdater.checkAtStartup();
tray.overlayKey = cfg.overlayKey;
},
);
uIOhook.start();
console.log("uIOhook started");
const port = await startServer(appUpdater, logger);
// TODO: move up (currently crashes)
logger.write(
`info ${os.type()} ${os.release} / v${app.getVersion()}`,
);
overlay.loadAppPage(port);
tray.serverPort = port;
},
// fixes(linux): window is black instead of transparent
process.platform === "linux" ? 1000 : 0,
);
});
})();
} else {
app.on("ready", async () => {
tray = new AppTray(eventPipe);
const logger = new Logger(eventPipe);
const gameLogWatcher = new GameLogWatcher(eventPipe, logger);
const gameConfig = new GameConfig(eventPipe, logger);
const poeWindow = new GameWindow();
const appUpdater = new AppUpdater(eventPipe);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _httpProxy = new HttpProxy(server, logger);
if (process.env.VITE_DEV_SERVER_URL) {
try {
await installExtension(VUEJS_DEVTOOLS);
logger.write("info Vue Devtools installed");
} catch (error) {
logger.write(`error installing Vue Devtools: ${error}`);
console.log(`error installing Vue Devtools: ${error}`);
}
}
process.addListener("uncaughtException", (err) => {
logger.write(`error [uncaughtException] ${err.message}, ${err.stack}`);
});
process.addListener("unhandledRejection", (reason) => {
logger.write(`error [unhandledRejection] ${(reason as Error).stack}`);
});
setTimeout( setTimeout(
async () => { async () => {
const overlay = new OverlayWindow(eventPipe, logger, poeWindow); const overlay = new OverlayWindow(eventPipe, logger, poeWindow);
@@ -199,4 +128,4 @@ if (process.platform === "darwin") {
process.platform === "linux" ? 1000 : 0, process.platform === "linux" ? 1000 : 0,
); );
}); });
} })();

View File

@@ -63,8 +63,6 @@ export function stashSearch(
overlay.assertGameActive(); overlay.assertGameActive();
clipboard.writeText(text); clipboard.writeText(text);
uIOhook.keyTap(Key.F, [Key.Ctrl]); uIOhook.keyTap(Key.F, [Key.Ctrl]);
// HACK: While https://www.pathofexile.com/forum/view-thread/3854775
uIOhook.keyTap(Key.Backspace);
uIOhook.keyTap(Key.V, [ uIOhook.keyTap(Key.V, [
process.platform === "darwin" ? Key.Meta : Key.Ctrl, process.platform === "darwin" ? Key.Meta : Key.Ctrl,

View File

@@ -33,7 +33,7 @@ export class OverlayWindow {
if (process.argv.includes("--no-overlay")) return; if (process.argv.includes("--no-overlay")) return;
const windowOpts: Electron.BrowserWindowConstructorOptions = { this.window = new BrowserWindow({
icon: path.join(__dirname, process.env.STATIC!, "icon.png"), icon: path.join(__dirname, process.env.STATIC!, "icon.png"),
...OVERLAY_WINDOW_OPTS, ...OVERLAY_WINDOW_OPTS,
width: 800, width: 800,
@@ -43,9 +43,7 @@ export class OverlayWindow {
webviewTag: true, webviewTag: true,
spellcheck: false, spellcheck: false,
}, },
}; });
this.window = new BrowserWindow(windowOpts);
this.window.setMenu( this.window.setMenu(
Menu.buildFromTemplate([ Menu.buildFromTemplate([
@@ -65,7 +63,6 @@ export class OverlayWindow {
this.window.webContents.setWindowOpenHandler((details) => { this.window.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url); shell.openExternal(details.url);
return { action: "deny" }; return { action: "deny" };
}); });
} }
@@ -90,10 +87,6 @@ export class OverlayWindow {
assertOverlayActive = () => { assertOverlayActive = () => {
if (!this.isInteractable) { if (!this.isInteractable) {
this.isInteractable = true; this.isInteractable = true;
// Linux needs explicit focus management
if (process.platform === "linux" && this.window) {
this.window.focus();
}
OverlayController.activateOverlay(); OverlayController.activateOverlay();
this.poeWindow.isActive = false; this.poeWindow.isActive = false;
} }
@@ -102,10 +95,6 @@ export class OverlayWindow {
assertGameActive = () => { assertGameActive = () => {
if (this.isInteractable) { if (this.isInteractable) {
this.isInteractable = false; this.isInteractable = false;
// Linux needs to release focus explicitly
if (process.platform === "linux" && this.window) {
this.window.blur();
}
OverlayController.focusTarget(); OverlayController.focusTarget();
this.poeWindow.isActive = true; this.poeWindow.isActive = true;
} }
@@ -164,11 +153,11 @@ export class OverlayWindow {
private handleOverlayAttached = (hasAccess?: boolean) => { private handleOverlayAttached = (hasAccess?: boolean) => {
if (hasAccess === false) { if (hasAccess === false) {
this.logger.write( this.logger.write(
"error [Overlay] PoE is running with administrator rights", "error [Overlay] PoE2 is running with administrator rights",
); );
dialog.showErrorBox( dialog.showErrorBox(
"PoE window - No access", "PoE2 window - No access",
// ---------------------- // ----------------------
"Path of Exile 2 is running with administrator rights.\n" + "Path of Exile 2 is running with administrator rights.\n" +
"\n" + "\n" +

View File

@@ -57,6 +57,7 @@
"has_empty_prefix": "前綴", "has_empty_prefix": "前綴",
"has_empty_suffix": "後綴", "has_empty_suffix": "後綴",
"item_level": "物品等級:{0}", "item_level": "物品等級:{0}",
"requires_level": "需求 等級: {0}",
"stock": "庫存:{0}", "stock": "庫存:{0}",
"map_tier": "地圖階級:{0}", "map_tier": "地圖階級:{0}",
"area_level": "區域等級:{0}", "area_level": "區域等級:{0}",
@@ -90,6 +91,7 @@
"mod_explicit": "隨機詞墜", "mod_explicit": "隨機詞墜",
"mod_crafted": "工藝", "mod_crafted": "工藝",
"mod_scourge": "災厄", "mod_scourge": "災厄",
"mod_rune": "Augment",
"unidentified": "未鑑定", "unidentified": "未鑑定",
"veiled": "隱匿", "veiled": "隱匿",
"foil_unique": "貼模傳奇", "foil_unique": "貼模傳奇",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: '中', RARITY_NORMAL: '中',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) 現在等級 (?<level>\d+)$/, LOG_LEVEL_UP: /^(.*) 現在等級 (?<level>\d+)$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^需求: \s*(?:等級[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*力量)?\D*(?:(?<dex>\d+)[^\d,]*敏捷)?\D*(?:(?<int>\d+)[^\d,]*智慧)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -57,6 +57,7 @@
"has_empty_prefix": "Präfix", "has_empty_prefix": "Präfix",
"has_empty_suffix": "Suffix", "has_empty_suffix": "Suffix",
"item_level": "Gegenstandsstufe: {0}", "item_level": "Gegenstandsstufe: {0}",
"requires_level": "Erfordert Stufe: {0}",
"stock": "Bestand: {0}", "stock": "Bestand: {0}",
"map_tier": "Kartenstufe: {0}", "map_tier": "Kartenstufe: {0}",
"area_level": "Gebietsstufe: {0}", "area_level": "Gebietsstufe: {0}",
@@ -90,6 +91,7 @@
"mod_explicit": "Explizit", "mod_explicit": "Explizit",
"mod_crafted": "Hergestellt", "mod_crafted": "Hergestellt",
"mod_scourge": "Plage", "mod_scourge": "Plage",
"mod_rune": "Augmentations",
"unidentified": "Nicht identifiziert", "unidentified": "Nicht identifiziert",
"veiled": "Verschleiert", "veiled": "Verschleiert",
"foil_unique": "Foil Unique", "foil_unique": "Foil Unique",
@@ -207,8 +209,8 @@
"tag_explicit_delve": "Delve", "tag_explicit_delve": "Delve",
"tag_explicit_veiled": "Verschleiert", "tag_explicit_veiled": "Verschleiert",
"tag_explicit_incursion": "Incursion", "tag_explicit_incursion": "Incursion",
"tag_rune": "Rune", "tag_rune": "Augmentations",
"tag_added_rune": "Rune", "tag_added_rune": "Augmentations",
"tag_sanctum": "Sanctum", "tag_sanctum": "Sanctum",
"tag_desecrated": "Entweihtes", "tag_desecrated": "Entweihtes",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'Normal', RARITY_NORMAL: 'Normal',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) ist jetzt Stufe (?<level>\d+)$/, LOG_LEVEL_UP: /^(.*) ist jetzt Stufe (?<level>\d+)$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^Erfordert: \s*(?:Stufe[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*Str)?\D*(?:(?<dex>\d+)[^\d,]*Ges )?\D*(?:(?<int>\d+)[^\d,]*Int)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -57,6 +57,7 @@
"has_empty_prefix": "Prefix", "has_empty_prefix": "Prefix",
"has_empty_suffix": "Suffix", "has_empty_suffix": "Suffix",
"item_level": "Item Level: {0}", "item_level": "Item Level: {0}",
"requires_level": "Req. Level: {0}",
"stock": "Stock: {0}", "stock": "Stock: {0}",
"map_tier": "Map Tier: {0}", "map_tier": "Map Tier: {0}",
"area_level": "Area Level: {0}", "area_level": "Area Level: {0}",
@@ -91,6 +92,7 @@
"mod_explicit": "Explicit", "mod_explicit": "Explicit",
"mod_crafted": "Crafted", "mod_crafted": "Crafted",
"mod_scourge": "Scourge", "mod_scourge": "Scourge",
"mod_rune": "Augment",
"unidentified": "Unidentified", "unidentified": "Unidentified",
"veiled": "Veiled", "veiled": "Veiled",
"foil_unique": "Foil Unique", "foil_unique": "Foil Unique",
@@ -208,8 +210,8 @@
"tag_explicit_delve": "Delve", "tag_explicit_delve": "Delve",
"tag_explicit_veiled": "Veiled", "tag_explicit_veiled": "Veiled",
"tag_explicit_incursion": "Incursion", "tag_explicit_incursion": "Incursion",
"tag_rune": "Rune", "tag_rune": "Augment",
"tag_added_rune": "Rune", "tag_added_rune": "Augment",
"tag_sanctum": "Sanctum", "tag_sanctum": "Sanctum",
"tag_desecrated": "Desecrated", "tag_desecrated": "Desecrated",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'Normal', RARITY_NORMAL: 'Normal',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) is now level (?<level>\d+)$/, LOG_LEVEL_UP: /^(.*) is now level (?<level>\d+)$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^Requires: \s*(?:Level[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*Str)?\D*(?:(?<dex>\d+)[^\d,]*Dex)?\D*(?:(?<int>\d+)[^\d,]*Int)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -57,6 +57,7 @@
"has_empty_prefix": "Prefijo", "has_empty_prefix": "Prefijo",
"has_empty_suffix": "Sufijo", "has_empty_suffix": "Sufijo",
"item_level": "Nivel del objeto: {0}", "item_level": "Nivel del objeto: {0}",
"requires_level": "Requiere Nivel: {0}",
"stock": "Inventario: {0}", "stock": "Inventario: {0}",
"map_tier": "Nivel del mapa: {0}", "map_tier": "Nivel del mapa: {0}",
"area_level": "Nivel del área: {0}", "area_level": "Nivel del área: {0}",
@@ -90,6 +91,7 @@
"mod_explicit": "Explícito", "mod_explicit": "Explícito",
"mod_crafted": "Fabricado", "mod_crafted": "Fabricado",
"mod_scourge": "Calamidad", "mod_scourge": "Calamidad",
"mod_rune": "mejora",
"unidentified": "No identificado", "unidentified": "No identificado",
"veiled": "Velado", "veiled": "Velado",
"foil_unique": "Único Brillante", "foil_unique": "Único Brillante",
@@ -207,8 +209,8 @@
"tag_explicit_delve": "Excursión", "tag_explicit_delve": "Excursión",
"tag_explicit_veiled": "Velado", "tag_explicit_veiled": "Velado",
"tag_explicit_incursion": "Incursión", "tag_explicit_incursion": "Incursión",
"tag_rune": "Runa", "tag_rune": "mejora",
"tag_added_rune": "Runa", "tag_added_rune": "mejora",
"tag_sanctum": "Sanctum", "tag_sanctum": "Sanctum",
"tag_desecrated": "profanado", "tag_desecrated": "profanado",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'Normal', RARITY_NORMAL: 'Normal',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) es ahora nivel (?<level>\d+)$/, LOG_LEVEL_UP: /^(.*) es ahora nivel (?<level>\d+)$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^Requiere: \s*(?:Nivel[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*Fue)?\D*(?:(?<dex>\d+)[^\d,]*Des)?\D*(?:(?<int>\d+)[^\d,]*Int)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -462,6 +462,30 @@
], ],
"items": [] "items": []
}, },
{
"query": [
"ITEM::Regal Shard"
],
"items": [
"ITEM::Regal Orb"
]
},
{
"query": [
"ITEM::Artificer's Shard"
],
"items": [
"ITEM::Artificer's Orb"
]
},
{
"query": [
"ITEM::Chance Shard"
],
"items": [
"ITEM::Orb of Chance"
]
},
{ {
"query": [ "query": [
"ITEM::Inscribed Ultimatum" "ITEM::Inscribed Ultimatum"

View File

@@ -468,6 +468,30 @@
], ],
"items": [] "items": []
}, },
{
"query": [
"ITEM::Regal Shard"
],
"items": [
"ITEM::Regal Orb"
]
},
{
"query": [
"ITEM::Artificer's Shard"
],
"items": [
"ITEM::Artificer's Orb"
]
},
{
"query": [
"ITEM::Chance Shard"
],
"items": [
"ITEM::Orb of Chance"
]
},
// Trials // Trials
{ {
"query": [ "query": [

View File

@@ -57,6 +57,7 @@
"has_empty_prefix": "プレフィックス", "has_empty_prefix": "プレフィックス",
"has_empty_suffix": "サフィックス", "has_empty_suffix": "サフィックス",
"item_level": "アイテムレベル: {0}", "item_level": "アイテムレベル: {0}",
"requires_level": "装備条件 レベル: {0}",
"stock": "在庫: {0}", "stock": "在庫: {0}",
"map_tier": "マップティア: {0}", "map_tier": "マップティア: {0}",
"area_level": "エリアレベル: {0}", "area_level": "エリアレベル: {0}",
@@ -90,6 +91,7 @@
"mod_explicit": "明示", "mod_explicit": "明示",
"mod_crafted": "クラフト", "mod_crafted": "クラフト",
"mod_scourge": "スカージ", "mod_scourge": "スカージ",
"mod_rune": "オーグメント",
"unidentified": "未鑑定", "unidentified": "未鑑定",
"veiled": "ヴェール", "veiled": "ヴェール",
"foil_unique": "フォイルユニーク", "foil_unique": "フォイルユニーク",
@@ -203,8 +205,8 @@
"tag_explicit_delve": "デルヴ", "tag_explicit_delve": "デルヴ",
"tag_explicit_veiled": "ヴェール", "tag_explicit_veiled": "ヴェール",
"tag_explicit_incursion": "インカージョン", "tag_explicit_incursion": "インカージョン",
"tag_rune": "ルーン", "tag_rune": "オーグメント",
"tag_added_rune": "ルーン", "tag_added_rune": "オーグメント",
"tag_sanctum": "Sanctum", "tag_sanctum": "Sanctum",
"tag_desecrated": "冒涜", "tag_desecrated": "冒涜",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'ノーマル', RARITY_NORMAL: 'ノーマル',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*)は現在レベル(?<level>\d+)です$/, LOG_LEVEL_UP: /^(.*)は現在レベル(?<level>\d+)です$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^装備条件:\s*(?:レベル[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*筋力)?\D*(?:(?<dex>\d+)[^\d,]*器用さ)?\D*(?:(?<int>\d+)[^\d,]*知性)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -54,6 +54,7 @@
"has_empty_suffix": "빈 접미어", "has_empty_suffix": "빈 접미어",
"spirit": "정신력: {0}", "spirit": "정신력: {0}",
"item_level": "아이템 레벨: {0}", "item_level": "아이템 레벨: {0}",
"requires_level": "요구 사항 레벨: {0}",
"stock": "홈: {0}", "stock": "홈: {0}",
"map_tier": "지도 등급: {0}", "map_tier": "지도 등급: {0}",
"area_level": "지역 레벨: {0}", "area_level": "지역 레벨: {0}",
@@ -87,6 +88,7 @@
"mod_explicit": "부여된 속성", "mod_explicit": "부여된 속성",
"mod_crafted": "제작된 속성", "mod_crafted": "제작된 속성",
"mod_scourge": "스컬지", "mod_scourge": "스컬지",
"mod_rune": "증강물",
"unidentified": "미확인", "unidentified": "미확인",
"veiled": "장막 속성", "veiled": "장막 속성",
"foil_unique": "반짝이", "foil_unique": "반짝이",
@@ -200,8 +202,8 @@
"tag_explicit_delve": "탐광", "tag_explicit_delve": "탐광",
"tag_explicit_veiled": "장막", "tag_explicit_veiled": "장막",
"tag_explicit_incursion": "기습", "tag_explicit_incursion": "기습",
"tag_rune": "", "tag_rune": "증강물",
"tag_added_rune": "", "tag_added_rune": "증강물",
"tag_sanctum": "성역", "tag_sanctum": "성역",
"tag_desecrated": "훼손된", "tag_desecrated": "훼손된",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: '일반', RARITY_NORMAL: '일반',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*)의 레벨이 (?<level>\d+)이(가) 되었습니다$/, LOG_LEVEL_UP: /^(.*)의 레벨이 (?<level>\d+)이(가) 되었습니다$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^요구 사항: \s*(?:레벨[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*힘)?\D*(?:(?<dex>\d+)[^\d,]*민첩)?\D*(?:(?<int>\d+)[^\d,]*지능)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -56,6 +56,7 @@
"has_empty_prefix": "Prefixo", "has_empty_prefix": "Prefixo",
"has_empty_suffix": "Sufixo", "has_empty_suffix": "Sufixo",
"item_level": "Nível do item: {0}", "item_level": "Nível do item: {0}",
"requires_level": "Requer Nível: {0}",
"stock": "Inventário: {0}", "stock": "Inventário: {0}",
"map_tier": "Nível do mapa: {0}", "map_tier": "Nível do mapa: {0}",
"area_level": "Nível da área: {0}", "area_level": "Nível da área: {0}",
@@ -89,6 +90,7 @@
"mod_explicit": "Explícito", "mod_explicit": "Explícito",
"mod_crafted": "Fabricado", "mod_crafted": "Fabricado",
"mod_scourge": "Flagelo", "mod_scourge": "Flagelo",
"mod_rune": "aprimoramento",
"unidentified": "Não identificado", "unidentified": "Não identificado",
"veiled": "Velado", "veiled": "Velado",
"foil_unique": "Único Brilhante", "foil_unique": "Único Brilhante",
@@ -201,7 +203,7 @@
"tag_explicit_delve": "Exploração", "tag_explicit_delve": "Exploração",
"tag_explicit_veiled": "Velado", "tag_explicit_veiled": "Velado",
"tag_explicit_incursion": "Incursão", "tag_explicit_incursion": "Incursão",
"tag_rune": "Runa", "tag_rune": "aprimoramento",
"tag_desecrated": "Profanado" "tag_desecrated": "Profanado"
}, },
"online_filter": { "online_filter": {

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'Normal', RARITY_NORMAL: 'Normal',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) agora está no nível (?<level>\d+)$/, LOG_LEVEL_UP: /^(.*) agora está no nível (?<level>\d+)$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^Requer: \s*(?:Nível[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*For)?\D*(?:(?<dex>\d+)[^\d,]*Des)?\D*(?:(?<int>\d+)[^\d,]*Int)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -71,6 +71,7 @@
"has_empty_prefix": "Префикс", "has_empty_prefix": "Префикс",
"has_empty_suffix": "Суффикс", "has_empty_suffix": "Суффикс",
"item_level": "Ур. предмета: {0}", "item_level": "Ур. предмета: {0}",
"requires_level": "Требуется Уровень: {0}",
"spirit": "Дух: {0}", "spirit": "Дух: {0}",
"reload_time": "Время перезарядки: {0}", "reload_time": "Время перезарядки: {0}",
"stock": "Запас: {0}", "stock": "Запас: {0}",
@@ -106,6 +107,7 @@
"mod_explicit": "Свойство", "mod_explicit": "Свойство",
"mod_crafted": "Мастерский", "mod_crafted": "Мастерский",
"mod_scourge": "Преображённое", "mod_scourge": "Преображённое",
"mod_rune": "усилителей",
"unidentified": "Неопознанный", "unidentified": "Неопознанный",
"veiled": "Завуалирован", "veiled": "Завуалирован",
"foil_unique": "Реликвия", "foil_unique": "Реликвия",
@@ -219,8 +221,8 @@
"tag_explicit_delve": "Спуск", "tag_explicit_delve": "Спуск",
"tag_explicit_veiled": "Завуалирован", "tag_explicit_veiled": "Завуалирован",
"tag_explicit_incursion": "Вмешательство", "tag_explicit_incursion": "Вмешательство",
"tag_rune": "Руна", "tag_rune": "усилителей",
"tag_added_rune": "Руна", "tag_added_rune": "усилителей",
"tag_sanctum": "Святилище", "tag_sanctum": "Святилище",
"tag_desecrated": "Очернённый", "tag_desecrated": "Очернённый",
"tag_skill": "Skill", "tag_skill": "Skill",

View File

@@ -1,4 +1,5 @@
// @ts-check // @ts-check
// autogenerated file, do not edit
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */ /** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default { export default {
RARITY_NORMAL: 'Обычный', RARITY_NORMAL: 'Обычный',
@@ -159,4 +160,5 @@ export default {
LOG_LEVEL_UP: /^(.*) теперь (?<level>\d+) уровня$/, LOG_LEVEL_UP: /^(.*) теперь (?<level>\d+) уровня$/,
// [Manual] // [Manual]
LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/, LOG_ZONE_GEN: /^Generating level (?<area_level>\d+) area "(?<zone>.*)" with seed (?<seed>\d+)$/,
REQUIRES_LINE: /^Требуется: \s*(?:Уровень[^\d,]*(?<level>\d+))?\D*(?:(?<str>\d+)[^\d,]*Сила)?\D*(?:(?<dex>\d+)[^\d,]*Ловк)?\D*(?:(?<int>\d+)[^\d,]*Инт)?$/,
} }

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
import { __testExports, ParserState } from "@/parser/Parser";
import { beforeEach, describe, expect, it } from "vitest";
import { setupTests } from "@specs/vitest.setup";
import {
FracturedItem,
FracturedItemNoModMarked,
RareItem,
TestItem,
} from "./items";
import { loadForLang } from "@/assets/data";
import { ParsedItem } from "@/parser/ParsedItem";
import { ModifierType } from "@/parser/modifiers";
describe("Parse Fractured Items", () => {
beforeEach(async () => {
setupTests();
await loadForLang("en");
});
it.each([
[FracturedItem, true],
[FracturedItemNoModMarked, true],
[RareItem, undefined],
])(
"%#, Each mod section is recognized",
(item: TestItem, isFractured: boolean | undefined) => {
const sections = __testExports.itemTextToSections(item.rawText);
const parsedItem = {} as ParsedItem;
__testExports.parseFracturedText(
sections[sections.length - 1],
parsedItem,
);
expect(parsedItem.isFractured).toBe(isFractured);
},
);
it("adds fractured if some mod is fractured", () => {
const parsedItem = {
newMods: [
{
info: { type: ModifierType.Fractured, tags: [] },
stats: [],
},
{
info: { type: ModifierType.Explicit, tags: [] },
stats: [],
},
{
info: { type: ModifierType.Explicit, tags: [] },
stats: [],
},
],
} as unknown as ParserState;
__testExports.parseFractured(parsedItem);
expect(parsedItem.isFractured).toBe(true);
});
it("does nothing if no mod is fractured", () => {
const parsedItem = {
newMods: [
{
info: { type: ModifierType.Implicit, tags: [] },
stats: [],
},
{
info: { type: ModifierType.Explicit, tags: [] },
stats: [],
},
],
} as unknown as ParserState;
__testExports.parseFractured(parsedItem);
expect(parsedItem.isFractured).toBeUndefined();
});
});

View File

@@ -66,6 +66,14 @@ export class TestItem implements ParsedItem {
note?: string; note?: string;
category?: ItemCategory | undefined; category?: ItemCategory | undefined;
requires?: {
level: number;
str: number;
dex: number;
int: number;
};
info: BaseType = { info: BaseType = {
name: "test", name: "test",
refName: "test", refName: "test",
@@ -125,6 +133,12 @@ NormalItem.quality = 9;
NormalItem.armourAR = 174; NormalItem.armourAR = 174;
NormalItem.armourES = 60; NormalItem.armourES = 60;
NormalItem.itemLevel = 81; NormalItem.itemLevel = 81;
NormalItem.requires = {
level: 75,
str: 67,
dex: 0,
int: 67,
};
NormalItem.info.refName = "Divine Crown"; NormalItem.info.refName = "Divine Crown";
NormalItem.sectionCount = 4; NormalItem.sectionCount = 4;
@@ -157,6 +171,12 @@ MagicItem.weaponELEMENTAL = MagicItem.weaponLIGHTNING;
MagicItem.weaponCRIT = 5; MagicItem.weaponCRIT = 5;
MagicItem.weaponAS = 1.2; MagicItem.weaponAS = 1.2;
MagicItem.itemLevel = 32; MagicItem.itemLevel = 32;
MagicItem.requires = {
level: 28,
str: 57,
dex: 0,
int: 0,
};
MagicItem.info.refName = "Temple Maul"; MagicItem.info.refName = "Temple Maul";
MagicItem.sectionCount = 5; MagicItem.sectionCount = 5;
@@ -200,6 +220,12 @@ RareItem.weaponELEMENTAL =
RareItem.weaponAS = 1.2; RareItem.weaponAS = 1.2;
RareItem.weaponCRIT = 5; RareItem.weaponCRIT = 5;
RareItem.itemLevel = 80; RareItem.itemLevel = 80;
RareItem.requires = {
level: 51,
str: 0,
dex: 103,
int: 0,
};
RareItem.info.refName = "Rider Bow"; RareItem.info.refName = "Rider Bow";
RareItem.sectionCount = 5; RareItem.sectionCount = 5;
@@ -237,6 +263,12 @@ UniqueItem.category = ItemCategory.Focus;
UniqueItem.rarity = ItemRarity.Unique; UniqueItem.rarity = ItemRarity.Unique;
UniqueItem.armourES = 44; UniqueItem.armourES = 44;
UniqueItem.itemLevel = 81; UniqueItem.itemLevel = 81;
UniqueItem.requires = {
level: 26,
str: 0,
dex: 0,
int: 43,
};
// NOTE: requires step through to verify use of Name here is right // NOTE: requires step through to verify use of Name here is right
UniqueItem.info.refName = "The Eternal Spark"; UniqueItem.info.refName = "The Eternal Spark";
@@ -271,6 +303,12 @@ Item Level: 79
RareWithImplicit.category = ItemCategory.Ring; RareWithImplicit.category = ItemCategory.Ring;
RareWithImplicit.rarity = ItemRarity.Rare; RareWithImplicit.rarity = ItemRarity.Rare;
RareWithImplicit.itemLevel = 79; RareWithImplicit.itemLevel = 79;
RareWithImplicit.requires = {
level: 45,
str: 0,
dex: 0,
int: 0,
};
RareWithImplicit.info.refName = "Prismatic Ring"; RareWithImplicit.info.refName = "Prismatic Ring";
RareWithImplicit.sectionCount = 5; RareWithImplicit.sectionCount = 5;
@@ -401,6 +439,12 @@ HighDamageRareItem.sectionCount = 9;
HighDamageRareItem.prefixCount = 3; HighDamageRareItem.prefixCount = 3;
HighDamageRareItem.suffixCount = 3; HighDamageRareItem.suffixCount = 3;
HighDamageRareItem.implicitCount = 1; HighDamageRareItem.implicitCount = 1;
HighDamageRareItem.requires = {
level: 79,
str: 89,
dex: 89,
int: 0,
};
HighDamageRareItem.runeSockets = { HighDamageRareItem.runeSockets = {
empty: 0, empty: 0,
@@ -447,6 +491,12 @@ ArmourHighValueRareItem.rarity = ItemRarity.Rare;
ArmourHighValueRareItem.quality = 20; ArmourHighValueRareItem.quality = 20;
ArmourHighValueRareItem.armourAR = 3075; ArmourHighValueRareItem.armourAR = 3075;
ArmourHighValueRareItem.itemLevel = 80; ArmourHighValueRareItem.itemLevel = 80;
ArmourHighValueRareItem.requires = {
level: 65,
str: 121,
dex: 0,
int: 0,
};
ArmourHighValueRareItem.info.refName = "Soldier Cuirass"; ArmourHighValueRareItem.info.refName = "Soldier Cuirass";
ArmourHighValueRareItem.sectionCount = 8; ArmourHighValueRareItem.sectionCount = 8;
@@ -488,6 +538,12 @@ Note: ~b/o 5 exalted
WandRareItem.category = ItemCategory.Wand; WandRareItem.category = ItemCategory.Wand;
WandRareItem.rarity = ItemRarity.Rare; WandRareItem.rarity = ItemRarity.Rare;
WandRareItem.itemLevel = 82; WandRareItem.itemLevel = 82;
WandRareItem.requires = {
level: 90,
str: 0,
dex: 0,
int: 125,
};
WandRareItem.info.refName = "Withered Wand"; WandRareItem.info.refName = "Withered Wand";
WandRareItem.sectionCount = 6; WandRareItem.sectionCount = 6;
@@ -521,6 +577,12 @@ NormalShield.itemLevel = 82;
NormalShield.armourAR = 71; NormalShield.armourAR = 71;
NormalShield.armourEV = 64; NormalShield.armourEV = 64;
NormalShield.armourBLOCK = 25; NormalShield.armourBLOCK = 25;
NormalShield.requires = {
level: 54,
str: 42,
dex: 42,
int: 0,
};
NormalShield.info.refName = "Polished Targe"; NormalShield.info.refName = "Polished Targe";
NormalShield.sectionCount = 6; NormalShield.sectionCount = 6;
@@ -558,6 +620,12 @@ Has 2(1-3) Charm Slots
TwoImplicitItem.category = ItemCategory.Belt; TwoImplicitItem.category = ItemCategory.Belt;
TwoImplicitItem.rarity = ItemRarity.Rare; TwoImplicitItem.rarity = ItemRarity.Rare;
TwoImplicitItem.itemLevel = 80; TwoImplicitItem.itemLevel = 80;
TwoImplicitItem.requires = {
level: 59,
str: 0,
dex: 0,
int: 0,
};
TwoImplicitItem.info.refName = "Ornate Belt"; TwoImplicitItem.info.refName = "Ornate Belt";
TwoImplicitItem.sectionCount = 5; TwoImplicitItem.sectionCount = 5;
@@ -686,5 +754,147 @@ RareMapFakeAllProps.mapMagicMonsters = 30;
RareMapFakeAllProps.mapRareMonsters = 71; RareMapFakeAllProps.mapRareMonsters = 71;
RareMapFakeAllProps.mapDropChance = 90; RareMapFakeAllProps.mapDropChance = 90;
RareMapFakeAllProps.mapItemRarity = 17; RareMapFakeAllProps.mapItemRarity = 17;
RareMapFakeAllProps.sectionCount = 5; RareMapFakeAllProps.sectionCount = 6;
// #endregion
// #region FracturedItem
export const FracturedItem = new TestItem(`Item Class: Bows
Rarity: Rare
Miracle Siege
Obliterator Bow
--------
Quality: +25% (augmented)
Physical Damage: 381-705 (augmented)
Critical Hit Chance: 9.40% (augmented)
Attacks per Second: 1.15
--------
Requires: Level 78, 163 (unmet) Dex
--------
Sockets: S S
--------
Item Level: 81
--------
36% increased Physical Damage (rune)
--------
{ Implicit Modifier }
50% reduced Projectile Range
--------
{ Prefix Modifier "Flaring" (Tier: 1) — Damage, Physical, Attack }
Adds 32(26-39) to 59(44-66) Physical Damage (fractured)
{ Prefix Modifier "Bloodthirsty" (Tier: 4) — Damage, Physical, Attack }
134(110-134)% increased Physical Damage
{ Prefix Modifier "Champion's" (Tier: 4) — Damage, Physical, Attack }
54(45-54)% increased Physical Damage
+113(98-123) to Accuracy Rating
{ Suffix Modifier "of the Essence" — Speed }
20(20-25)% chance to gain Onslaught on Killing Hits with this Weapon
{ Suffix Modifier "of the Essence" — Attack }
+3 to Level of all Attack Skills
{ Suffix Modifier "of Ruin" (Tier: 2) — Attack, Critical }
+4.4(3.81-4.4)% to Critical Hit Chance
--------
Fractured Item
`);
FracturedItem.category = ItemCategory.Bow;
FracturedItem.rarity = ItemRarity.Rare;
FracturedItem.quality = 25;
FracturedItem.weaponPHYSICAL = 624;
FracturedItem.weaponAS = 1.15;
FracturedItem.weaponCRIT = 9.4;
FracturedItem.itemLevel = 81;
FracturedItem.requires = {
level: 78,
str: 163,
dex: 0,
int: 0,
};
FracturedItem.info.refName = "Obliterator Bow";
FracturedItem.isFractured = true;
FracturedItem.prefixCount = 3;
FracturedItem.suffixCount = 3;
FracturedItem.implicitCount = 1;
FracturedItem.sectionCount = 9;
FracturedItem.runeSockets = {
empty: 0,
current: 2,
normal: 2,
};
// #endregion
// #region FracturedItemNoModMarked
export const FracturedItemNoModMarked = new TestItem(`Item Class: Bows
Rarity: Rare
Miracle Siege
Obliterator Bow
--------
Quality: +25% (augmented)
Physical Damage: 381-705 (augmented)
Critical Hit Chance: 9.40% (augmented)
Attacks per Second: 1.15
--------
Requires: Level 78, 163 (unmet) Dex
--------
Sockets: S S
--------
Item Level: 81
--------
36% increased Physical Damage (rune)
--------
{ Implicit Modifier }
50% reduced Projectile Range
--------
{ Prefix Modifier "Flaring" (Tier: 1) — Damage, Physical, Attack }
Adds 32(26-39) to 59(44-66) Physical Damage
{ Prefix Modifier "Bloodthirsty" (Tier: 4) — Damage, Physical, Attack }
134(110-134)% increased Physical Damage
{ Prefix Modifier "Champion's" (Tier: 4) — Damage, Physical, Attack }
54(45-54)% increased Physical Damage
+113(98-123) to Accuracy Rating
{ Suffix Modifier "of the Essence" — Speed }
20(20-25)% chance to gain Onslaught on Killing Hits with this Weapon
{ Suffix Modifier "of the Essence" — Attack }
+3 to Level of all Attack Skills
{ Suffix Modifier "of Ruin" (Tier: 2) — Attack, Critical }
+4.4(3.81-4.4)% to Critical Hit Chance
--------
Fractured Item
`);
FracturedItemNoModMarked.category = ItemCategory.Bow;
FracturedItemNoModMarked.rarity = ItemRarity.Rare;
FracturedItemNoModMarked.quality = 25;
FracturedItemNoModMarked.weaponPHYSICAL = 381.5;
FracturedItemNoModMarked.weaponAS = 1.15;
FracturedItemNoModMarked.weaponCRIT = 9.4;
FracturedItemNoModMarked.itemLevel = 81;
FracturedItemNoModMarked.requires = {
level: 78,
str: 163,
dex: 0,
int: 0,
};
FracturedItemNoModMarked.info.refName = "Obliterator Bow";
FracturedItemNoModMarked.isFractured = true;
FracturedItemNoModMarked.prefixCount = 3;
FracturedItemNoModMarked.suffixCount = 3;
FracturedItemNoModMarked.implicitCount = 1;
FracturedItemNoModMarked.sectionCount = 9;
FracturedItemNoModMarked.runeSockets = {
empty: 0,
current: 2,
normal: 2,
};
// #endregion // #endregion

View File

@@ -1,3 +1,4 @@
import { CLIENT_STRINGS as _$ } from "@/assets/data";
import { __testExports } from "@/parser/Parser"; import { __testExports } from "@/parser/Parser";
import { beforeEach, describe, expect, it, test } from "vitest"; import { beforeEach, describe, expect, it, test } from "vitest";
import { setupTests } from "@specs/vitest.setup"; import { setupTests } from "@specs/vitest.setup";
@@ -8,6 +9,7 @@ import {
NormalItem, NormalItem,
RareItem, RareItem,
RareWithImplicit, RareWithImplicit,
TestItem,
UniqueItem, UniqueItem,
WandRareItem, WandRareItem,
} from "./items"; } from "./items";
@@ -49,6 +51,8 @@ describe("parseWeapon", () => {
const res = __testExports.parseWeapon(sections[1], parsedItem); const res = __testExports.parseWeapon(sections[1], parsedItem);
// console.log(sections);
expect(res).toBe("SECTION_PARSED"); expect(res).toBe("SECTION_PARSED");
expect(parsedItem.weaponPHYSICAL).toBe(MagicItem.weaponPHYSICAL); expect(parsedItem.weaponPHYSICAL).toBe(MagicItem.weaponPHYSICAL);
expect(parsedItem.weaponELEMENTAL).toBe(MagicItem.weaponELEMENTAL); expect(parsedItem.weaponELEMENTAL).toBe(MagicItem.weaponELEMENTAL);
@@ -134,3 +138,86 @@ describe("parseArmour", () => {
expect(parsedItem.armourBLOCK).toBe(ArmourHighValueRareItem.armourBLOCK); expect(parsedItem.armourBLOCK).toBe(ArmourHighValueRareItem.armourBLOCK);
}); });
}); });
describe("parseRequirements", () => {
it.each([
["Normal", NormalItem],
["Magic", MagicItem],
["Rare", RareItem],
["Unique", UniqueItem],
["RareWithImplicit", RareWithImplicit],
["HighDamageRare", HighDamageRareItem],
["ArmourHighValueRare", ArmourHighValueRareItem],
["WandRare", WandRareItem],
])(
"%s, items parse requirements",
async (testName: string, item: TestItem) => {
setupTests();
await loadForLang("en");
const sections = __testExports.itemTextToSections(item.rawText);
const parsedItem = {} as ParsedItem;
const res = __testExports.parseRequirements(
sections.find((s) => s.some((l) => l.startsWith(_$.REQUIRES)))!,
parsedItem,
);
expect(res).toBe("SECTION_PARSED");
expect(parsedItem.requires).toEqual(item.requires);
},
);
it.each([
[
"en",
"Requires: Level 28, 57 (augmented) Str",
{ level: 28, str: 57, dex: 0, int: 0 },
],
[
"cmn-Hant",
"需求: 等級 80, 108 (unmet) 智慧",
{ level: 80, str: 0, dex: 0, int: 108 },
],
[
"ja",
"装備条件:レベル 72, 70 筋力, 70 知性",
{ level: 72, str: 70, dex: 0, int: 70 },
],
[
"ko",
"요구 사항: 레벨 78, 89 힘, 89 (unmet) 민첩",
{ level: 78, str: 89, dex: 89, int: 0 },
],
[
"cmn-Hant",
"需求: 等級 78, 54 力量, 138 智慧",
{ level: 78, str: 54, dex: 0, int: 138 },
],
[
"ru",
"Требуется: Уровень 80, 59 (unmet) Ловк, 59 Инт",
{
level: 80,
str: 0,
dex: 59,
int: 59,
},
],
])(
"%s requires regex works",
async (
lang: string,
str: string,
expectedResult: ParsedItem["requires"],
) => {
setupTests();
await loadForLang(lang);
const parsedItem = {} as ParsedItem;
const res = __testExports.parseRequirements([str], parsedItem);
expect(res).toBe("SECTION_PARSED");
expect(parsedItem.requires).toEqual(expectedResult);
},
);
});

View File

@@ -245,6 +245,7 @@ export interface TranslationDict {
LOG_ZONE_GEN: RegExp; LOG_ZONE_GEN: RegExp;
DOUBLE_CORRUPTED: string; DOUBLE_CORRUPTED: string;
IMPLICIT_MODIFIER: string; IMPLICIT_MODIFIER: string;
REQUIRES_LINE: RegExp;
} }
export interface Filter { export interface Filter {

View File

@@ -94,6 +94,12 @@ export interface ParsedItem {
}; };
note?: string; note?: string;
category?: ItemCategory; category?: ItemCategory;
requires?: {
level: number;
str: number;
dex: number;
int: number;
};
info: BaseType; info: BaseType;
rawText: string; rawText: string;
} }

View File

@@ -48,7 +48,7 @@ type SectionParseResult =
type ParserFn = (section: string[], item: ParserState) => SectionParseResult; type ParserFn = (section: string[], item: ParserState) => SectionParseResult;
type VirtualParserFn = (item: ParserState) => Result<never, string> | void; type VirtualParserFn = (item: ParserState) => Result<never, string> | void;
interface ParserState extends ParsedItem { export interface ParserState extends ParsedItem {
name: string; name: string;
baseType: string | undefined; baseType: string | undefined;
infoVariants: BaseType[]; infoVariants: BaseType[];
@@ -379,6 +379,7 @@ function parseBlightedMap(item: ParsedItem) {
} }
function parseFractured(item: ParserState) { function parseFractured(item: ParserState) {
// NOTE: partially also controlled by parseFracturedText
if (item.newMods.some((mod) => mod.info.type === ModifierType.Fractured)) { if (item.newMods.some((mod) => mod.info.type === ModifierType.Fractured)) {
item.isFractured = true; item.isFractured = true;
} }
@@ -580,15 +581,26 @@ function parseItemLevel(section: string[], item: ParsedItem) {
} }
function parseRequirements(section: string[], item: ParsedItem) { function parseRequirements(section: string[], item: ParsedItem) {
if ( if (!section[0].startsWith(_$.REQUIRES)) {
section[0].startsWith(_$.REQUIREMENTS) ||
section[0].startsWith(_$.REQUIRES)
) {
return "SECTION_PARSED";
}
return "SECTION_SKIPPED"; return "SECTION_SKIPPED";
} }
const match = section[0].match(_$.REQUIRES_LINE);
// TODO: remove once validated in other langs
if (!match) {
throw new Error("Failed to parse requirements");
}
item.requires = {
level: parseInt(match.groups!.level ?? "0"),
str: parseInt(match.groups!.str ?? "0"),
dex: parseInt(match.groups!.dex ?? "0"),
int: parseInt(match.groups!.int ?? "0"),
};
return "SECTION_PARSED";
}
function parseTalismanTier(section: string[], item: ParsedItem) { function parseTalismanTier(section: string[], item: ParsedItem) {
if (section[0].startsWith(_$.TALISMAN_TIER)) { if (section[0].startsWith(_$.TALISMAN_TIER)) {
item.talismanTier = Number(section[0].slice(_$.TALISMAN_TIER.length)); item.talismanTier = Number(section[0].slice(_$.TALISMAN_TIER.length));
@@ -1222,9 +1234,11 @@ function parsePriceNote(section: string[], item: ParsedItem) {
return "SECTION_SKIPPED"; return "SECTION_SKIPPED";
} }
function parseFracturedText(section: string[], _item: ParsedItem) { function parseFracturedText(section: string[], item: ParsedItem) {
for (const line of section) { for (const line of section) {
if (line === _$.FRACTURED_ITEM) { if (line === _$.FRACTURED_ITEM) {
// HACK: remove once bug is fixed (https://www.pathofexile.com/forum/view-thread/3891367)
item.isFractured = true;
return "SECTION_PARSED"; return "SECTION_PARSED";
} }
} }
@@ -1754,4 +1768,7 @@ export const __testExports = {
parseArmour, parseArmour,
parseModifiers, parseModifiers,
parseWaystone, parseWaystone,
parseRequirements,
parseFractured,
parseFracturedText,
}; };

View File

@@ -61,6 +61,11 @@
:filter="filters.itemLevel" :filter="filters.itemLevel"
:name="t('item.item_level')" :name="t('item.item_level')"
/> />
<filter-btn-numeric
v-if="filters.requires?.level"
:filter="filters.requires?.level"
:name="t('item.requires_level')"
/>
<filter-btn-numeric <filter-btn-numeric
v-if="filters.stackSize" v-if="filters.stackSize"
:filter="filters.stackSize" :filter="filters.stackSize"

View File

@@ -1,10 +1,5 @@
import type { ItemFilters } from "./interfaces"; import type { ItemFilters } from "./interfaces";
import { import { ParsedItem, ItemCategory, ItemRarity } from "@/parser";
ParsedItem,
ItemCategory,
ItemRarity,
itemIsModifiable,
} from "@/parser";
import { tradeTag } from "../trade/common"; import { tradeTag } from "../trade/common";
import { ModifierType } from "@/parser/modifiers"; import { ModifierType } from "@/parser/modifiers";
import { BaseType, ITEM_BY_REF } from "@/assets/data"; import { BaseType, ITEM_BY_REF } from "@/assets/data";
@@ -294,6 +289,22 @@ export function createFilters(
}; };
} }
if (item.requires && item.rarity === ItemRarity.Rare && !opts.exact) {
if (
item.requires.level &&
item.requires.level <= 75 &&
item.itemLevel &&
item.itemLevel <= 75
) {
filters.requires = {
level: {
value: item.requires.level,
disabled: true,
},
};
}
}
const forAdornedJewel = const forAdornedJewel =
item.rarity === ItemRarity.Magic && item.rarity === ItemRarity.Magic &&
// item.isCorrupted && -- let the buyer corrupt // item.isCorrupted && -- let the buyer corrupt
@@ -302,8 +313,8 @@ export function createFilters(
if ( if (
!item.isUnmodifiable && !item.isUnmodifiable &&
// Ignore tablet since only corrupted are rares, and we want to compare to them // Ignore waystones now(prev tablets) since if there is one that is corrupted with right mods buyer wont care
item.category !== ItemCategory.Tablet && item.category !== ItemCategory.Map &&
(item.rarity === ItemRarity.Normal || (item.rarity === ItemRarity.Normal ||
item.rarity === ItemRarity.Magic || item.rarity === ItemRarity.Magic ||
item.rarity === ItemRarity.Rare || item.rarity === ItemRarity.Rare ||
@@ -355,12 +366,12 @@ export function createFilters(
filters.sanctified = { disabled: false }; filters.sanctified = { disabled: false };
} }
if (!item.isFractured && itemIsModifiable(item)) { if (!item.isFractured && opts.exact) {
filters.fractured = { value: false }; filters.fractured = { value: false };
} }
if (item.isFoil) { if (item.isFoil) {
filters.foil = { disabled: false }; filters.foil = { disabled: true };
} }
if (item.influences.length && item.influences.length <= 2) { if (item.influences.length && item.influences.length <= 2) {

View File

@@ -89,6 +89,12 @@ export interface ItemFilters {
collapseListings: "api" | "app"; collapseListings: "api" | "app";
}; };
tempRuneStorage?: StatFilter[]; tempRuneStorage?: StatFilter[];
requires?: {
level?: FilterNumeric;
str?: FilterNumeric;
dex?: FilterNumeric;
int?: FilterNumeric;
};
} }
export interface FilterNumeric { export interface FilterNumeric {

View File

@@ -25,7 +25,6 @@ export class Cache {
if (!currency || this.currency === currency) return; if (!currency || this.currency === currency) return;
this.currency = currency; this.currency = currency;
this.cached.clear(); this.cached.clear();
console.log("Purged cache");
} }
static deriveTtl(...limits: RateLimiter[]): number { static deriveTtl(...limits: RateLimiter[]): number {

View File

@@ -433,6 +433,18 @@ export function createTradeRequest(
} }
} }
if (
filters.requires &&
filters.requires.level &&
!filters.requires.level.disabled
) {
propSet(
query.filters,
"req_filters.filters.lvl.max",
filters.requires.level.value,
);
}
if (filters.quality && !filters.quality.disabled) { if (filters.quality && !filters.quality.disabled) {
propSet( propSet(
query.filters, query.filters,