Compare commits

...

344 Commits

Author SHA1 Message Date
Kvan7
edf7c8f7ac Merge pull request #772 from Kvan7:dev
v0.13.3
2025-12-13 07:30:34 -06:00
kvan7
9fc0854958 version bump 2025-12-13 07:28:03 -06:00
kvan7
fe5dfb6e10 fix invalid regex 2025-12-13 07:25:46 -06:00
Kvan7
4bd7f4eb21 Merge pull request #770 from Kvan7/dev
v0.13.2
2025-12-13 03:55:10 -06:00
kvan7
706ef2c921 Whitespace optional 2025-12-13 03:50:55 -06:00
kvan7
a005c1d733 fix mod line 2025-12-13 03:43:04 -06:00
kvan7
eafd795abc version bump 2025-12-13 03:19:01 -06:00
kvan7
9416cbbc54 data 2025-12-13 03:16:40 -06:00
Kvan7
0d98945530 Merge pull request #768 from Kvan7:dev
v0.13.1
2025-12-12 14:03:03 -06:00
kvan7
867bde022a data 2025-12-12 13:57:18 -06:00
kvan7
b4fd783634 bump 2025-12-12 13:45:28 -06:00
kvan7
c60b93a052 temp disable rune selector 2025-12-12 13:28:46 -06:00
kvan7
3b49e32d9d inserted newlines 2025-12-12 13:22:12 -06:00
kvan7
a4c24a9985 data update 2025-12-12 13:19:03 -06:00
Kvan7
17899c5c30 Merge pull request #767 from Kvan7:dev
v0.13.0
2025-12-12 09:17:24 -06:00
kvan7
6a5a603405 fix ts error 2025-12-12 09:06:17 -06:00
kvan7
ae6fe133fe bump version and fix pre-commits 2025-12-12 09:02:05 -06:00
kvan7
3d55b9ac0a change toggle when maps 2025-12-12 08:42:23 -06:00
kvan7
6d84612b71 disable tablet change while not live 2025-12-12 07:54:52 -06:00
kvan7
3bfc3fca2a fix lineage gems not showing quick price 2025-12-12 07:50:37 -06:00
Kvan7
ab98903edf Features/leveling (#766)
* Add opt-in log reading

* add regex and zone detection

* add widget for leveling

* generic rigging setup

* penalty

* attach settings view

* add client log reading

* slight refactor of widget name

* Connect xp to logs

* Settings options and i18n

* update styles

* PR fixes
2025-12-11 23:19:10 -06:00
kvan7
57f696918f Fix gold map mod 2025-12-08 21:12:32 -06:00
kvan7
4c0bbf815c Change curr selector to be radio
Fixes #765
2025-12-08 20:10:49 -06:00
kvan7
c2d85a74eb add abyss to tablet use stats
Fixes #764
2025-12-08 19:20:10 -06:00
kvan7
9ccec71e1c Check matching currency quick price
Fixes #762
2025-12-08 19:07:14 -06:00
Kvan7
ecd84efa45 Features/addChaosToBulk (#763)
* add components

* Fit in 3 icons

* Add support for correct bulk api online types

* fix league setting on bulk not working
2025-12-07 21:29:36 -06:00
Kvan7
5c5d075e13 Features/marketInfo (#761)
* Add volumes to trends

* Add rounding large numbers

* Add settings for volume amount

* Apply settings

* Add volume max currency

* Add ninja link on volume

* i18n

* comments
2025-12-07 15:12:37 -06:00
Kvan7
f397d67e7e Features/pushPrivateParser (#759)
Adds copy of parser from private repo
2025-12-06 22:53:27 -06:00
kvan7
2179b55588 update ninja interfaces for non currency item support 2025-12-05 15:37:07 -06:00
kvan7
edb177f012 Revert "asdf"
This reverts commit 925b43fe1f.
2025-12-04 22:13:33 -06:00
kvan7
925b43fe1f asdf 2025-12-04 22:12:45 -06:00
kvan7
bdd11522b4 asdf 2025-12-04 22:12:21 -06:00
Kvan7
c853181e00 Merge pull request #757 from Kvan7/testBranch
TestBranch
2025-12-04 22:01:08 -06:00
kvan7
83014075fa fix test 2025-12-04 21:59:39 -06:00
kvan7
e4acfb7139 run precommit on all 2025-12-04 21:59:38 -06:00
kvan7
8f96d9b59f some failing test 2025-12-04 21:59:38 -06:00
kvan7
c19849edfd Merge commit '40ac84515a949a10b30240126c2a1e973bad7261' into dev 2025-12-04 21:59:23 -06:00
kvan7
40ac84515a update names 2025-12-04 21:55:25 -06:00
kvan7
4cb035de9e Merge commit 'f0c1b82424976f614f12dc63d91d0759407ae018' into dev 2025-12-04 21:34:08 -06:00
kvan7
f7d05d600c waystonr 2025-12-04 21:33:48 -06:00
Kvan7
f0c1b82424 action 2025-12-04 21:25:56 -06:00
kvan7
16ee1b44c8 drops 2025-12-04 10:55:44 -06:00
kvan7
5b96e7dd5c fix: issue with showing "ex" when autocurrency returned in divs 2025-12-03 17:35:36 -06:00
kvan7
9c7802048d data update 2025-12-01 13:35:06 -06:00
kvan7
262896d69d Enable related items and trends 2025-11-29 21:31:44 -06:00
kvan7
ff2e5e9afe add fracture false #753 2025-11-29 19:19:57 -06:00
kvan7
de30c0b70e Clean up linux focusing 2025-11-29 12:44:35 -06:00
kvan7
c89f058fac revert character length since poe2 still is at 50 :( 2025-11-29 11:45:21 -06:00
kvan7
63e518a532 FIx poe2db search endpoint requiring token 2025-11-29 11:42:07 -06:00
kvan7
9bcfabcf6a Fix unit test 2025-11-29 10:46:20 -06:00
kvan7
75ae6ac704 fix unit tests due to dropped files 2025-11-29 10:43:40 -06:00
kvan7
23a409beb4 Add talismans 2025-11-28 19:05:40 -06:00
kvan7
344e09ee30 all data maybe 2025-11-28 16:00:19 -06:00
kvan7
3ed9bface9 data 2025-11-27 22:05:12 -06:00
kvan7
1b9e674391 start on updating item drop 2025-11-26 22:13:06 -06:00
kvan7
c3d27edad1 Update to new proxy endpoint 2025-11-26 20:20:19 -06:00
kvan7
1431ce6a06 Fully add support for multiple currencies? 2025-10-11 11:01:31 -05:00
kvan7
e95ebe6576 Move primary to prices and core currency 2025-10-10 07:34:06 -05:00
kvan7
b83a2ad4ef Adds swapping of currency 2025-10-10 07:11:44 -05:00
kvan7
f0f90769ed Add base currency setting 2025-10-08 21:26:17 -05:00
kvan7
8212d217d4 Adds count filter for uses remaining 2025-10-07 21:48:04 -05:00
kvan7
b5ef198494 Add Map Searching 2025-10-05 16:44:43 -05:00
Kvan7
69d729c8e2 Merge pull request #740 from Kvan7:dev
v0.12.8
2025-09-30 22:19:18 -05:00
kvan7
e90d2cc479 version bump 2025-09-30 22:18:56 -05:00
kvan7
0e4df22ccb data again 2025-09-30 22:12:04 -05:00
kvan7
5bb09fc7c7 hide new help text for tablets 2025-09-30 21:46:44 -05:00
kvan7
4b0ff4c4d8 Fix tablet parsing 2025-09-30 21:34:17 -05:00
kvan7
6533f8cb81 drop uncut gem stuff 2025-09-30 21:33:55 -05:00
kvan7
0fa2323ecd data update 2025-09-30 20:30:26 -05:00
kvan7
b19f5949a8 remove maps from bulk exchange searches 2025-09-29 21:53:31 -05:00
kvan7
617391177c Hide empty mod filter on maps 2025-09-29 21:34:41 -05:00
kvan7
257161e993 Add link in docs to #673 2025-09-29 21:31:47 -05:00
Kvan7
b31dbcb5ab Merge pull request #739 from Kvan7:dev
v0.12.7
2025-09-29 07:00:08 -05:00
kvan7
ab13ffdf15 version bump 2025-09-29 06:44:37 -05:00
kvan7
78ae83d2cf Fix normalized showing 0 when should be undefined 2025-09-29 06:40:01 -05:00
kvan7
eb7227afaf Fix crafting open modifiers counting and filter
Fixes #727
2025-09-28 20:49:26 -05:00
kvan7
edb97e84ff [Bug]: Heroic Tragedy and Undying Hate
Fixes #720
2025-09-28 12:58:36 -05:00
kvan7
61b9967bea [Bug]: Wrong Itemlevel is set when trying to pricecheck Spiked Spear
Fixes #736
2025-09-27 09:38:24 -05:00
kvan7
b874962614 Update poe2db to go to mods page 2025-09-24 20:13:52 -05:00
kvan7
3c0a533395 prevent undefined 2025-09-24 19:01:22 -05:00
kvan7
4297289951 Add filter by price and basetype 2025-09-24 18:57:43 -05:00
Kvan7
4af188c4c3 Merge pull request #721 from Kvan7:dev
v0.12.6
2025-09-23 22:01:11 -05:00
kvan7
d9902767dd version bump 2025-09-23 22:00:47 -05:00
kvan7
22f7e93f04 Fix tier not showing on crit 2025-09-23 21:50:44 -05:00
kvan7
567066a27a [Bug]: Temporalis skill cooldown not using correct max/min
Fixes #713
and
[Bug]: reduced move speed penalty inverted
Fixes #716
2025-09-23 21:46:27 -05:00
kvan7
201dcd4bad Add mods showing to rare base items when low mod count 2025-09-23 21:08:17 -05:00
kvan7
c937dcd523 [Bug]: Pseudo life treats the life increase from Strength incorrectly
Fixes #707
2025-09-23 20:48:53 -05:00
kvan7
56c66394f3 Fix hiding res on uniques 2025-09-23 20:42:17 -05:00
kvan7
8b41960f90 make annoints appear much less 2025-09-23 19:40:04 -05:00
Kvan7
345d290f9a Merge pull request #719 from Kvan7:dev
v0.12.5
2025-09-22 20:55:49 -05:00
kvan7
b91583a90e version bump 2025-09-22 20:50:49 -05:00
kvan7
f0551bf6fd [Bug]: Idol of Estazunti treated as currency when not on CE
Fixes #711
2025-09-22 20:43:47 -05:00
kvan7
4f542e86a3 [Bug]: Uruk's Smelting not found in poe ninja results
Fixes #710
2025-09-22 20:27:02 -05:00
kvan7
756103ef04 Make tablets less confusing 2025-09-22 20:17:45 -05:00
kvan7
69708f08d9 data update 2025-09-22 17:51:44 -05:00
kvan7
62aaae0bf2 Stop flooring logbook levels for now 2025-09-17 15:55:54 -05:00
Kvan7
7f3b5b8a12 Merge pull request #700 from Kvan7:dev
v0.12.4
2025-09-16 21:28:52 -05:00
kvan7
f83ffa9e25 Fix removed skill word for some reason? 2025-09-16 21:27:25 -05:00
kvan7
5ac3c8bd9d Fix quality showing when not really relevant 2025-09-16 21:21:02 -05:00
kvan7
c9536c3149 controller fix 2 maybe? 2025-09-16 21:15:38 -05:00
kvan7
a344d3e1cf version bump 2025-09-16 21:09:07 -05:00
kvan7
a59cd3ef9a Add skill level filter
Fixes #691
2025-09-16 21:02:03 -05:00
kvan7
e094751650 [Bug]: Damage above 1000 is split on comma
Fixes #696
2025-09-16 17:23:46 -05:00
kvan7
3eb7ef60a3 move tests stuff around a little 2025-09-16 15:42:32 -05:00
kvan7
97dc5c14c9 Data update fix morior invictus 2025-09-15 21:19:55 -05:00
kvan7
c369170608 Fix for upstream bug 2025-09-15 20:38:03 -05:00
kvan7
0bab4269ef add filter by buyout price 2025-09-15 18:32:29 -05:00
kvan7
3113946138 add note prop 2025-09-15 17:15:06 -05:00
kvan7
8c31fcbbec oops didn't have a div to test on 2025-09-15 17:13:37 -05:00
kvan7
3b3f80bc53 Drop any fixes for uncut gems
Fixes #628
2025-09-15 17:02:10 -05:00
kvan7
08f766005c drop "fromChat" 2025-09-15 17:00:18 -05:00
kvan7
e50c983988 Attempt to work around #471 2025-09-15 16:53:38 -05:00
Kvan7
7046bcec08 Merge pull request #689 from Kvan7:dev
v0.12.3
2025-09-14 21:56:01 -05:00
kvan7
6c5db5e0ca version bump 2025-09-14 21:55:45 -05:00
kvan7
754b86eedd enable psuedo on some uniques 2025-09-14 21:27:55 -05:00
kvan7
6a503b563e Fix unique variants 2025-09-14 21:16:25 -05:00
kvan7
84956a97f5 fix megalomanic only showing one annoint 2025-09-14 18:46:48 -05:00
kvan7
496cc344b5 [Bug]: The Unborn Lich Ravenous Staff parsing issue
Fixes #624
2025-09-14 17:54:23 -05:00
kvan7
e1db4b16ce hide runes on uniques 2025-09-14 17:27:40 -05:00
kvan7
962231b02a fixed stats 2025-09-14 17:12:00 -05:00
kvan7
ea108c4467 fix unique belt 2025-09-14 15:41:19 -05:00
kvan7
a9220171ca data 2025-09-14 15:34:18 -05:00
kvan7
160fe9811b right click remove defaults 2025-09-14 15:25:08 -05:00
kvan7
31e22cbfa6 fix ritual and flasks recovery 2025-09-14 15:13:52 -05:00
Kvan7
a065a4b595 Merge pull request #688 from Kvan7:features/normalizedPrice
Features/normalizedPrice
2025-09-14 15:01:35 -05:00
kvan7
993822cb8f final stuff 2025-09-14 14:52:11 -05:00
kvan7
3792a7dc06 show price 2025-09-14 11:33:43 -05:00
kvan7
b9f469e0d0 add normalized price to results 2025-09-14 11:27:09 -05:00
kvan7
930f2ad98c add cache 2025-09-14 10:35:23 -05:00
kvan7
faba6d52f3 Add price to interface 2025-09-14 09:36:50 -05:00
kvan7
31853cc5b3 add app_i18n 2025-09-14 09:13:54 -05:00
kvan7
944fc19ba8 remove useless null check 2025-09-14 09:02:14 -05:00
kvan7
0cfe20a49a change optional magic filter for lower ilvl 2025-09-14 09:01:17 -05:00
kvan7
f18150c1cf Revert "remove magic hidden since lowered requirements"
This reverts commit 9b7d89fa5a.
2025-09-14 08:57:09 -05:00
kvan7
37b8ff1c9f old installer 2025-09-13 17:36:34 -05:00
kvan7
9b7d89fa5a remove magic hidden since lowered requirements 2025-09-13 17:33:51 -05:00
kvan7
4eafcdc5b6 tablets better 2025-09-13 17:31:16 -05:00
kvan7
64330bee0b Fix non exact charm slot 2025-09-13 14:27:48 -05:00
kvan7
b292afa9db fix tablets showing bulk rates 2025-09-13 13:55:48 -05:00
kvan7
ebed50f084 more base item show up 2025-09-13 13:38:05 -05:00
kvan7
ecd2d6afa7 Add desecrated to count in empty 2025-09-13 12:55:15 -05:00
kvan7
88aa28574d desecrated maybe fix for consoles 2025-09-11 16:34:16 -05:00
kvan7
9f4582cb7d [Bug]: DPS calc broken in chinese language
Fixes #670
2025-09-11 16:31:49 -05:00
kvan7
6c014da832 [Bug]: Relic/Sanctum "Movement Speed" mod parsed wrong.
Fixes #678
2025-09-11 16:19:38 -05:00
Kvan7
8dae04129b Merge pull request #674 from DBosley/fix/linux-gnome-focus-issue
fix: Prevent focus stealing on Linux
2025-09-11 16:08:47 -05:00
Dave Bosley
ef389554fd fix: Prevent focus stealing on Linux/X11 with GNOME
Fixes #299 - Overlay no longer causes GNOME desktop to appear over fullscreen games.

Sets window type to 'toolbar' and manages focusable state dynamically on Linux.
2025-09-11 00:21:59 -06:00
Kvan7
7c62289019 Merge pull request #675 from Kvan7:dev
v0.12.2
2025-09-10 22:01:14 -05:00
kvan7
7abeb85016 version bump 2025-09-10 22:00:53 -05:00
kvan7
a6f6eb1d4f mark price fixed items 2025-09-10 21:43:19 -05:00
kvan7
5a34b0f5f8 change gone 2025-09-10 20:36:06 -05:00
kvan7
72c3a1a857 Hide desecrated mods 2025-09-10 20:29:30 -05:00
kvan7
3732270de9 next attempt at fixing installer issues 2025-09-10 16:30:56 -05:00
Kvan7
1992e28636 Merge pull request #663 from Kvan7:dev
v0.12.1
2025-09-10 06:13:01 -05:00
kvan7
5fd444c04f also hide added runes 2025-09-10 06:11:37 -05:00
kvan7
f3e1c7fee6 version bump 2025-09-10 06:02:05 -05:00
kvan7
cba5ba0b55 Chaos resistance is counting toward #% Total Elemental Resistance when price checking
Fixes #660
2025-09-10 05:31:34 -05:00
Kvan7
7782d98460 Merge pull request #658 from Kvan7:dev
v0.12.0
2025-09-09 23:34:43 -05:00
kvan7
c129260cdd banner 2025-09-09 23:33:22 -05:00
kvan7
f9b0b5a70d version bump 2025-09-09 23:26:05 -05:00
Kvan7
bbcefcadf0 Merge pull request #657 from Kvan7:ninja
Ninja
2025-09-09 23:11:31 -05:00
kvan7
202660ad33 increase retry timer to not ge super spammed 2025-09-09 22:50:09 -05:00
kvan7
81bd9bdeb4 Add it all in 2025-09-09 22:34:13 -05:00
kvan7
d4eed621d4 works? 2025-09-09 21:18:22 -05:00
kvan7
879282f349 push working tree pseudos active 2025-09-09 20:55:27 -05:00
kvan7
dc99645f9b forgot some 2025-09-09 20:55:00 -05:00
kvan7
779f8109f3 Add back all pseudos
Fixes #652
2025-09-09 20:43:23 -05:00
kvan7
f3497619b1 Remove weighted sum calcs
Fixes #654
2025-09-09 20:30:19 -05:00
kvan7
c823c28ae1 Add back all pseudos
Fixes #653
Fixes #655
2025-09-09 20:14:31 -05:00
kvan7
3360af1b1c Data update for pseudos 2025-09-09 20:00:10 -05:00
kvan7
f8f7a639a2 [Bug]: In Korean, critical strike chance for bows cannot be searched!
Fixes #644
2025-09-08 19:43:53 -05:00
kvan7
cb12d4ca7c [Bug]: When search the logbook, area level field doesn't work
Fixes #647
2025-09-08 16:11:07 -05:00
kvan7
538f4ea6ce [Bug]: Typo in Flying Spear implicit mod
Fixes #639
2025-09-07 20:08:51 -05:00
kvan7
3b6c214523 update bug report template 2025-09-07 09:25:04 -05:00
Kvan7
b30dc760bc Merge pull request #627 from Kvan7:dev
v0.11.6
2025-09-06 17:43:11 -05:00
kvan7
c6fc79f496 run linter on python file 2025-09-06 17:42:14 -05:00
kvan7
2a3a119480 delete commented code empty mod related 2025-09-06 17:38:48 -05:00
kvan7
428255cf6c add precommit stuff 2025-09-06 17:16:59 -05:00
kvan7
3cff973d03 add precommit 2025-09-06 16:28:32 -05:00
kvan7
f927c63ba5 [Bug]: Sanctified items
Fixes #619
2025-09-06 16:20:23 -05:00
kvan7
07a0840c79 add is modifiable 2025-09-06 15:15:40 -05:00
kvan7
94aadc8a7f [Not Recognized Modifier] - Attacks have 12% chance to cause Bleeding
Fixes #277
2025-09-06 12:13:17 -05:00
kvan7
5c2adf6aab add hidden filter for magic items below correct ilvl 2025-09-05 23:51:47 -05:00
kvan7
67ec519f3d fix quality showing where it really shouldn't 2025-09-05 23:22:56 -05:00
kvan7
0b29f09217 add empty or crafted to hidden 2025-09-05 23:03:00 -05:00
kvan7
5efc28e61d add common 2025-09-05 22:06:03 -05:00
kvan7
517be2cf9c [Bug]: Heart of the Well Diamond Unrevealed Parsing issue
Fixes #608
2025-09-05 20:06:52 -05:00
Kvan7
0930f8f0ba Merge pull request #611 from test137E29B/fix/fractured-item-parse 2025-09-05 14:56:11 -05:00
Jason
022918c5c6 Move parser lower 2025-09-06 04:40:40 +09:00
Jason
4b46f06ae8 Revert brackets 2025-09-06 03:48:21 +09:00
Jason
882bee2cb1 PR fixes 2025-09-06 03:47:26 +09:00
Jason
3e4cca92eb Fix incorrect parsing for Fractured Item text 2025-09-06 02:31:43 +09:00
Kvan7
2e2858c56b Merge pull request #609 from test137E29B/fix/non-en-exceptional-search 2025-09-05 09:20:34 -05:00
Jason
f10fafd523 Fix non English language Exceptional search 2025-09-05 23:17:35 +09:00
kvan7
4b11874a8b [Bug]: Desecrated mods are not visible in the mods of an item from the list of exhibited items at the flea market
Fixes #604
2025-09-05 06:32:53 -05:00
Kvan7
825ce48a11 Merge pull request #601 from Kvan7:dev
v0.11.5
2025-09-04 21:22:30 -05:00
kvan7
af588273c4 version bump 2025-09-04 21:21:39 -05:00
kvan7
fd37bf9b34 adds in demand and filters gone items 2025-09-04 21:18:43 -05:00
kvan7
7ddff1e69a revert version 2025-09-04 20:43:58 -05:00
kvan7
fe952b59d0 try to build it again maybe 2025-09-04 20:28:18 -05:00
Kvan7
c4fe9a0c5f Merge pull request #600 from Kvan7:dev
v0.11.4-test-fix-installer.0
2025-09-04 20:19:00 -05:00
kvan7
00a7563174 try updating builder 2025-09-04 20:18:15 -05:00
Kvan7
2ea75d58dc Merge pull request #599 from Kvan7:dev
v0.11.4
2025-09-04 20:09:25 -05:00
kvan7
a8516b9977 version bump 2025-09-04 20:08:04 -05:00
kvan7
e3bb5ac1ae [Bug]: Ingenuity is incorrectly detected as "Reduced effect of ring" instead of "increased effect of ring"
Fixes #586
2025-09-04 20:00:33 -05:00
kvan7
e71c4abdd4 add desecrated
Co-authored-by: Jason <badxnameless@gmail.com>
2025-09-04 19:46:07 -05:00
kvan7
8d88acbc92 remove perfect plus levels 2025-09-04 17:44:59 -05:00
kvan7
f0545acc73 add fractures back? 2025-09-04 17:42:10 -05:00
kvan7
d985abed62 Fix for exceptional white items
Co-authored-by: Jason <badxnameless@gmail.com>
2025-09-04 17:00:30 -05:00
kvan7
9a0f424462 add all white items to filter 2025-09-04 16:57:14 -05:00
kvan7
c079d9d34b data update 2025-09-04 16:57:05 -05:00
Kvan7
e91103cffe Merge pull request #570 from Kvan7:dev
v0.11.3
2025-08-29 17:17:25 -05:00
kvan7
f476b249b3 Add INSTANT BUYOUT 2025-08-29 17:15:35 -05:00
kvan7
60168c43ba Data Update 2025-08-29 15:06:49 -05:00
kvan7
15a8c5f38b add data update, known error on category 2025-08-29 13:54:27 -05:00
Kvan7
cc6b3544fb Merge pull request #568 from Kvan7:dev
v0.11.2
2025-08-29 09:38:31 -05:00
kvan7
af4d474148 version bump cause ci/cd 2025-08-29 09:37:34 -05:00
Kvan7
2d22836fbd Merge pull request #567 from Kvan7:dev
v0.11.1
2025-08-29 09:29:10 -05:00
kvan7
ecd9494fe7 Update workflow for outdated version 2025-08-29 09:27:59 -05:00
Kvan7
9407e3949f Merge pull request #566 from Kvan7/dev
v0.11.0
2025-08-29 09:23:57 -05:00
kvan7
8fff1551ef version bump 2025-08-29 07:20:38 -05:00
kvan7
75ae110071 comment stuff to prevent crash 2025-08-29 07:16:34 -05:00
kvan7
a1ae212978 make things a bit nicer for league start 2025-08-29 07:12:24 -05:00
kvan7
32b98801c6 Drop filter generator for 0.11 2025-08-27 16:05:39 -05:00
kvan7
be1b41a422 Update Base Item for new content
Fixes #559
2025-08-26 21:24:19 -05:00
kvan7
3824acba34 Emotion changes
Fixes #562
2025-08-25 19:56:05 -05:00
kvan7
ba16e53846 Parse rune by item equipment slot table
Fixes #560
2025-08-24 20:48:27 -05:00
kvan7
9798e104e8 Support soul core by item class 2025-08-24 19:28:56 -05:00
kvan7
b0ea4ed30c data update 2025-08-24 17:01:20 -05:00
kvan7
25d7775531 Update Online Filters
Fixes #556
2025-08-24 12:16:21 -05:00
kvan7
b98cf9e9d5 revert: Currency ratio
Removes currency ratio since a ton of new orbs are getting added
2025-08-24 10:06:19 -05:00
kvan7
361d51d61c Move UseTradeAPI out of vue component
Fixes #557
2025-08-23 22:04:16 -05:00
kvan7
f945161856 add 2 comments 2025-08-23 14:47:59 -05:00
kvan7
071c7de099 Get leagues programmatically
Fixes #564
2025-08-22 22:23:36 -05:00
kvan7
b0ceb4efde Mark merchant vs trade site listings
Fixes #563
2025-08-22 21:47:13 -05:00
kvan7
3a64e003f6 Feat: uncut gems fixed 2025-08-02 10:54:31 -05:00
kvan7
988c943372 fix: tests passing again 2025-07-27 15:11:28 -05:00
kvan7
46e3367341 [Bug]: Sockets not parsing on sceptre #550 2025-07-13 16:25:27 -05:00
Kvan7
8ee8c5ff71 Merge pull request #548 from Kvan7:dev
v0.10.3
2025-06-12 18:48:06 -05:00
kvan7
fa23ffbb68 version bump 2025-06-12 18:46:47 -05:00
kvan7
3644a6fdce Sockets on Bucklers #547 2025-06-12 18:45:40 -05:00
Kvan7
eac6c54370 Merge pull request #546 from Kvan7:dev
v0.10.2
2025-06-12 14:49:59 -05:00
kvan7
91f389d118 version bump 2025-06-12 14:47:30 -05:00
kvan7
0b53624908 Problem with Pseudo Mods #545 2025-06-12 14:40:20 -05:00
Kvan7
50363c70e5 Merge pull request #542 from Kvan7:dev
v0.10.1
2025-06-09 21:10:27 -05:00
kvan7
83e147b1df version bump 2025-06-09 21:07:47 -05:00
kvan7
598ce9f7d9 [Bug]: Item Compare is open on losing overlay focus #475 2025-06-09 21:01:53 -05:00
kvan7
ef22ab671a Fixes #540 #541 2025-06-09 20:32:20 -05:00
kvan7
8f8abaee0b fix: Missing monster ele res map stat 2025-06-07 09:59:03 -05:00
kvan7
3739bdbbec fix: Waystone Modifier "Increased Rarity of Items Found" #535
Fixes [Bug]: Waystone Modifier "Increased Rarity of Items Found" Incorrectly Duplicated Across Multiple Waystones #535
2025-06-07 09:51:14 -05:00
kvan7
5959501a14 feat: Adds block chance as equipment filter
Adds block chance for the equipment filter of shields and bucklers. also fixes a few issues with how bucklers were interpreted internally
2025-06-07 09:36:53 -05:00
Kvan7
98f56009eb Merge pull request #539 from Kvan7/dev
v0.10.0
2025-06-06 19:34:03 -05:00
Alexander Drozdov
d2ffa46892 update regex length 2025-06-06 18:54:34 -05:00
kvan7
9eb2f6c31d version bump 2025-06-06 18:51:59 -05:00
kvan7
5724df7239 missing pt refs 2025-06-06 18:38:41 -05:00
kvan7
97750a1c26 Add reload time filter 2025-06-06 18:28:27 -05:00
kvan7
eea37254b5 probably last data update for 0.2.1 2025-06-06 18:28:14 -05:00
kvan7
6aa288aaf0 initial data update 2025-06-05 21:47:50 -05:00
kvan7
150f78eff3 add Português 2025-06-01 11:29:24 -05:00
kvan7
7998f7ba40 Revert to poe1 tier numbering
Switches back to poe1 tier numbering as per 0.2.1
2025-06-01 10:16:04 -05:00
kvan7
c89e4f6810 [Bug]: Time-lost jewel range modifiers not handled correctly #531 2025-06-01 09:41:31 -05:00
kvan7
acd0484c7a data update 3 2025-06-01 09:13:51 -05:00
kvan7
7a177c6a28 more fix bad data 2025-05-30 19:38:45 -05:00
kvan7
e9395319df fix nan 2025-05-30 18:44:51 -05:00
kvan7
099bb1a9ad add deprecation notice to parser 2025-05-30 18:40:40 -05:00
kvan7
744d911603 Update data using new parser 2025-05-30 18:32:23 -05:00
kvan7
3f7b233c25 Update linux workflow for deprecated version 2025-04-22 18:23:26 -05:00
Kvan7
d71d33b5b9 Merge pull request #522 from Kvan7:dev
v0.9.4
2025-04-22 18:20:56 -05:00
kvan7
4e358430a9 version bump 2025-04-22 18:18:06 -05:00
kvan7
25f10c19b1 fix % stat for charm slots 2025-04-22 18:06:23 -05:00
kvan7
6ad747919e Update data 0.2.0f 2025-04-22 17:04:36 -05:00
Kvan7
1086ebc4f8 v0.9.3
v0.9.3
Merge pull request #505 from Kvan7:dev
2025-04-11 15:09:25 -05:00
kvan7
6aa0f2a71e Merge branch 'master' into dev 2025-04-11 15:09:02 -05:00
kvan7
015bef29f4 version bump 2025-04-11 15:08:07 -05:00
kvan7
ffccdb2f85 Fix rune selector in other languages 2025-04-11 13:48:25 -05:00
kvan7
40a5c8b7a8 missed version spot 2025-04-10 20:06:42 -05:00
kvan7
f22646bd77 Merge branch 'master' into dev 2025-04-10 20:05:49 -05:00
Kvan7
346b5c3f1e Merge pull request #503 from Kvan7:dev
v0.9.2
2025-04-10 20:04:43 -05:00
kvan7
4a1f0ed541 Merge remote-tracking branch 'origin/master' into dev 2025-04-10 20:04:30 -05:00
kvan7
9082490828 data update 0.2.0e 2025-04-10 20:03:45 -05:00
Kvan7
c8aa8935b2 Merge pull request #500 from Kvan7/dev
v0.9.1
2025-04-09 19:19:46 -05:00
kvan7
9abbc342d6 version bump 2025-04-09 19:16:26 -05:00
kvan7
6234f192e2 [Bug]: Fractured modifiers are not recognized #496 2025-04-09 19:05:42 -05:00
kvan7
7af6f40458 Simplify rune icon selector 2025-04-09 18:36:51 -05:00
kvan7
11b1031f40 Some fixes 2025-04-09 06:29:42 -05:00
Kvan7
27162cf6b0 v0.9.0 (#483)
* Price checking does not work when using a gamepad(Ctrl+D) #452

Uses parts from #454 to fix the issue.

Co-authored-by: lawrsp <7957003+lawrsp@users.noreply.github.com>

* Fix tests :(

* Fix magic rarity item name parse in "cmn-Hant" language (#460)

* Fix magic rarity item name parse in "cmn-Hant" language

* Add by translated when ref is null

---------

Co-authored-by: kvan7 <kvan.valve@gmail.com>

* chore: yarn to npm and add missing step (#461)

* fix: update game config path for linux to poe2

* Merge pull request #463 from nightah/fix-poe2-linuxconfig

fix: update game config path for linux to poe2

* fix tests

* fix: MacOS crash on startup (#428)

* fix: MacOS crash on startup

* update for windows/linux

* move main app startup into function
Mac calls that in async, other platforms proceed in sync.

* [PoE2] - Relics broken again #444

* test prettier and add npm script

* add format/lint support to main

* Fix defineProps macro

* Run speed v0.8.0 - Russian. #447

* fix negative gold

* Merge branch 'Kvan7:master' into master

* fix: app-ready fixing before we're ready

* Merge branch 'dev' into pr/larssn/428

* should work?

* Merge commit 'ab1c8bfa3a31b06da9cf18db0273f6a92e407bc5' into pr/larssn/428

* fix being lazy on the merge

* fix: add executable bit to compilation script (#465)

* Item Images (#472)

* image data stuff

* ignore lookup file

* update testing

* sort items

* change sort ot be by refname

* more code

* working pulling

* Fixes #456
Create script to request item's images from trade site #456

* Fixes #457
Update ImageFix to use new saved images before poe1 ones #457

* add images to items

* Add Spear as item category

* Add Flail as category

* Add "goodness" from upstream

* Fix #474
Tier # missing from some defense stats #474

* minor oops

* remove error for waystones

* Add a bunch of images to the docs

* Extra widgets docs

* Update chat commands links docs

* add stash search docs

* Item info docs page

* more docs

* Update bug-report.yml

* Version bump

* extra version bump

* Merge commit '77899fd6a7e08ab25bb677b70ba04757cc472f63' into dev

* Upgraded rune selector #345 (#482)

* Add added-rune mark to effected stats

* Add runes to data parser

* run data

* more change

* add popout to select rune

* Other runes work now

* different ui for opening rune menu

* banner for debug cause i want it

* Add above selector

* Add auto close

* Add config option to open above

* update translation docs

* More useful filter enables

* Add stat text to rune selector

* fix overflow

* fix le garbage

* Fix unique corrupted error

* hide runes in 2 spots

* fix ele damage runes

* Fix bug no filters

* minor change

* fix flat damage tiers

* fix source on ele damage

* revert #103

* data update for timelost

* fix warnings for timelost jewels

* add tags to items data

* Fix "grant skill" unidentified mod

* Update style for rune, remove logs

* better shortening for timelost text

* Generalize the rune selector

* Fix attack speed rune

* fix runes including non martial weapons

* sort trade ids, helps git struggle less

* final data update before patch time

* minor

* update data for 0.2.0

* fix runes kinda

* version bump
2025-04-04 15:25:33 -05:00
kvan7
77899fd6a7 fix doc image name caps 2025-03-27 09:58:15 -05:00
kvan7
1da7f727f9 fix docs build 2025-03-27 09:53:55 -05:00
Kvan7
e19356e97b v0.8.2 (#480)
* Price checking does not work when using a gamepad(Ctrl+D) #452

Uses parts from #454 to fix the issue.

Co-authored-by: lawrsp <7957003+lawrsp@users.noreply.github.com>

* Fix tests :(

* Fix magic rarity item name parse in "cmn-Hant" language (#460)

* Fix magic rarity item name parse in "cmn-Hant" language

* Add by translated when ref is null

---------

Co-authored-by: kvan7 <kvan.valve@gmail.com>

* chore: yarn to npm and add missing step (#461)

* fix: update game config path for linux to poe2

* fix tests

* fix: MacOS crash on startup (#428)

* fix: MacOS crash on startup

* update for windows/linux

* move main app startup into function
Mac calls that in async, other platforms proceed in sync.

* [PoE2] - Relics broken again #444

* test prettier and add npm script

* add format/lint support to main

* Fix defineProps macro

* Run speed v0.8.0 - Russian. #447

* fix negative gold

* Merge branch 'Kvan7:master' into master

* fix: app-ready fixing before we're ready

* Merge branch 'dev' into pr/larssn/428

* should work?

* Merge commit 'ab1c8bfa3a31b06da9cf18db0273f6a92e407bc5' into pr/larssn/428

* fix being lazy on the merge

* fix: add executable bit to compilation script (#465)

* Item Images (#472)

* image data stuff

* ignore lookup file

* update testing

* sort items

* change sort ot be by refname

* more code

* working pulling

* Fixes #456
Create script to request item's images from trade site #456

* Fixes #457
Update ImageFix to use new saved images before poe1 ones #457

* add images to items

* Add Spear as item category

* Add Flail as category

* Add "goodness" from upstream

* Fix #474
Tier # missing from some defense stats #474

* minor oops

* remove error for waystones

* Add a bunch of images to the docs

* Extra widgets docs

* Update chat commands links docs

* add stash search docs

* Item info docs page

* more docs

* Update bug-report.yml

* Version bump

* extra version bump

---------

Co-authored-by: lawrsp <7957003+lawrsp@users.noreply.github.com>
Co-authored-by: Seth Falco <seth@falco.fun>
Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
Co-authored-by: Lars <890725+larssn@users.noreply.github.com>
2025-03-27 09:50:27 -05:00
Kvan7
bfc0d209b3 Update bug-report.yml 2025-03-21 09:45:48 -05:00
Kvan7
75d96fb4b0 Update bug-report.yml 2025-03-05 18:00:54 -06:00
Kvan7
bc2861f099 Update config.yml 2025-03-05 17:58:03 -06:00
Kvan7
f5d7ee990f Create bug-report.yml 2025-03-05 17:42:00 -06:00
Kvan7
3e04f5aa7e Update issue templates 2025-03-05 17:29:23 -06:00
Kvan7
1058b6f40a v0.8.1 (#453)
* [PoE2] - Relics broken again #444

* test prettier and add npm script

* add format/lint support to main

* Fix defineProps macro

* Run speed v0.8.0 - Russian. #447

* fix negative gold

* fix hunters t7 accuracy

* fix note in ko

* fix dumb bow skills with curly braces

* version bump
2025-03-05 17:21:43 -06:00
Kvan7
8814f39112 v0.8.0 (#441)
* [PoE2] - Controlled metamorphisis shouldn't be ring size and up #397

* [Not Recognized Modifier] - quality on wand #398

* Wrong search parameters set for belt, generates negative min value #412

* Add Hybrid potential note to T1 tag #410

* Option to always show tiers #385

* Add oils to anoints #419

* Add deprecated notice

* better handling for hybrids

* Add corruption tags for those enchants #420

* Make normal rarity filter visible when applicable #408

* Fixes some tiers not showing due to being too high

* Add good crafting base items (magic) #423

* #424

* Should only be able to fill runes on a corrupted item that it has sockets #422

* fix crash for undefined error

* Adds hybrids to hybrid note

* Disable internal tradeIds for unique items #409

* fix empty mod always on

* [Parse Error] - Expedition logbook #318

* correct area level floors

* Request: add option for currency ratio. #396

* Fix add enchants to hover items

* Add sources for dps numbers

* Fix crit hit chance sources

* Revert "Add sources for dps numbers"

This reverts commit f1bbcc24a2.

* Change specific better ones

* bugfix/ubuntu/Fix widget area coordinates and size using primary display scale factor (#433)

* Multiplying widget are coordinates by the display scale factor

* Adding switch statement for handling linxu case properly

* adding missing default treshold

* Update main/src/windowing/WidgetAreaTracker.ts

* Handle not on primary display case

* rename

---------

Co-authored-by: kvan7 <kvan.valve@gmail.com>

* Fixes #435

* Fixes:  #436

* Seems to mostly fix tiers

* use ref again

* fix unique items broken in other languages

* Add max tier to show all tiers

* fixes some missing tiers

* fix corruption enchant tag again

* version bump
2025-02-27 18:20:52 -06:00
Kvan7
554a271e48 Fixes edit button not working on widgets (#400)
* Main fix

* fix artifact of bug in config file

* Version bump
2025-02-01 20:16:07 -06:00
kvan7
a72472d243 Fix default config 2025-02-01 10:50:47 -06:00
Kvan7
1a8942c7ec v0.7.0 (#392)
* Add config for default rune #347

* Jarngreipr - Wrong roll for life #343

* [PoE2] - Quality gets applied to corrupted items #369

* Addtional pdr not showing tier #371

* [Parse Error] - Unidentified items #360

* Filter to only Normal Rarity #350

* Add note to complex query error specifically #344

* continue script setup refactoring

* extract default config into widgets

* remove icon special cases

* continue moving files

* remove another special case

* Apply fixes for upstream update

* Add options to choose which numbering system to use #357

* Change to use "PoE2 tier numbering" #356

* Life regen tiers broken #361

* Update translations #321

* Add new strings

* Add field in settings to cycle through them all #359

* Add this box to some UIs #358

* More fixes for tips

* Features/elementalDamage (#387)

* Adds ele options

* add text and color

* dynamic display of ele filters

* fill default min value

* Filter actually changes now

* Add filter into site

* Increase ratio slightly

* [Parse Error] - Collector's Ritual Precursor Tablet of the Dogma #206

* Fix help text for tablets

* Update translations

* [Not Recognized Modifier] - Quality (Attribute Modifiers): +20% (augmented) #186

* remove unused setting

* version bump

* Controlled Metamorphosis is searched incorrectly after the trade site was updated #394

* hide tip 17 for now
2025-02-01 10:46:28 -06:00
Kvan7
61dcd4bcd0 v0.6.2 (#342)
* The calculated defense of an item is sometimes double (?) what it actually is #339

* [Not Recognized Modifier] - multiple Waystone modifiers #331

* version bump
2025-01-21 18:37:41 -06:00
Kvan7
e838549ee5 Update README.md 2025-01-20 08:28:28 -06:00
kvan7
5b054b24bb Update notice 2025-01-19 21:46:21 -06:00
Kvan7
92a949c015 update fraud notice 2025-01-19 21:27:21 -06:00
Kvan7
409b5b34a8 Fraud website notice 2025-01-19 21:26:20 -06:00
kvan7
32729fe38e v0.6.1
Fixes: Uniques stat ranges broken again #329
minor oopsy
2025-01-19 21:06:02 -06:00
Kvan7
850fa90455 v0.6.0 (#328)
* [Parse Error] - Rarity: Magic #285 (#315)

* Add in fix

Co-authored-by: @vagrant-soul

* Adds regression tests

* Features/uniqueRollsRewrite (#316)

* update with new rolls system

* More or less working now

* adds fix for some stats from json having +

* Features/jewels (#317)

* Fix help text on jewels

* Add fix for shock

* [Not Recognized Modifier] - (most monster map modifiers) #314

* Features/showItemOnHover (#322)

* Move row into own component

* add tooltip on shift and hover

* works MVP

* style and fix affix strings

* pseudo add

* change tooltip method to more dynamic

* Corrupted back to red

* style updates

* Add option for tooltip in settings

* Features/relics (#323)

* update en strings

* translation client strings

* Fix relic parse error

* Tag text

* enable relic filters by default

* add spanish? (#324)


Co-authored-by: @mgallego

* Divider line shows when it shouldn't on non weapons or armour #325

* Features/newRunes (#326)

* Rip out old rune code

* final removed stuff?

* change filter back to number

* use new rune filter

* done runes

* Remove alpha

* Features/runeSocketsPart4 (#327)

* some stat change is working

* iron working?

* new betterer way?

* DONE DONE DONE

* add translation line

* version bump
2025-01-19 19:53:35 -06:00
Kvan7
06c396f0cc v0.5.0 (#308)
* change discord id #278

* Update config & data

* Commit mockup image

* update mockup

* feat: filter quick update (#247)

* feat: filter generator in renderer

* feat: filter generator in main

* chore: some polishing

* feat: about section

* feat: i18n (-ish), only en text

* chore: cleanup

* chore: clarification regarding original filter

* chore: safety checks to ignore empty or not full identifier, or empty filters all together

* fix: linting fixes

* chore: locked version of winreg

* fix: dont hide divine orbs :O

* fix: set filter to hide would always hide it, even after turning it back
fix: updated config version

* fix: moved ignore low lvl area items down in filter order

* fix: drop winreg in favor of existing solution

* Change config file handling

* add in empty string check

* feat: changing filter generator into filter updater

* chore: simplify generator pages

* chore: removed code that is no longer needed

* chore: linter fixes

* fix: remove debug text

* feat: customizable folder path

* Add russian localization

* Merge commit 'ec35412d2694eb6a677415893776939b496b8aae'

* Revert "Merge commit 'ec35412d2694eb6a677415893776939b496b8aae'"

This reverts commit 8a3d84af91.

* Merge branch 'dev' into pr/tmakuch/247

* update config version

* Add note about case sensitivity

* run linter/formatter

* Features/germanLang (#300)

* add app_i18n.json

Co-authored-by: @professorspoon

* Update files

* official site

* Build files

* update description parser?

* [Not Recognized Modifier] - %phys #298

* add new unique stats, don't have roll values yet

* Update uniques again

* [PoE2] - Checking log book hard crashes #305

* run data script again?

* data files update for 0.1.1

* version bump
2025-01-16 15:57:50 -06:00
Kvan7
d02f086f19 v0.4.2 (#274)
* Unique rollable value parsing #225

* Controlled Metamorphosis - search by ring size #256

* [Not Recognized Modifier] - +2 Charm Slots #257

* Acknowledgement option to hide "is BETA" warning #269

* Fixes map not recognized for base text

* version bump

* Request: option to select/populate all stats #246
2025-01-11 08:32:49 -06:00
kvan7
5e9b4f4703 v0.4.1 2025-01-10 09:36:14 -06:00
Kvan7
b1a8d15f7d v0.4.0 (#268)
* fix russian es

* Features/modTiers (#266)

* Fixes hybrid not selecting by default

* Remove ratio warning banner

* [Not Recognized Modifier] - Note: ~price explicit — Not recognized modifier #255

* feature: Try Pseudos (#261)

* feature: Weighting resistances

* Merge branch 'dev' into master

* Change to button

* Wraps up moving to a button

* Fix bug with "weapon" category

* update parser to remove duplicated ref

* data hopefully not bricked

* version bump
2025-01-10 08:19:59 -06:00
Kvan7
4951ac9e2d v0.3.1 (#260)
* Corrupted items pricecheck #252

* Features/tests (#254)

* Basic test working

* LETS GO TESTING

* 0.1.0f bump

* version bump & linter fixes
2025-01-08 22:10:59 -06:00
Kvan7
ad10eb5f64 v0.3.0 (#245)
* Should empty rune sockets be disabled by default? #223

* fix table format (#216)

* Features/onlineFilterAlert #220 (#238)

* Add custom sort for ex/div ratio

* Add tooltip

* Adds warning banner

* done

* fix: added armour tag to focus (#241)

Co-authored-by: Your Name <your.email@example.com>

* Features/sockets (#244)

* Start of work to change items mods

* push stuff

* console

* kinda works?

* Works enough

* Fix unique ref name #232 #239

* Fix unique hiding mods

* enable unique by default

* Fix damage not always correctly selected

* add rune tag style, update warning banner

* update banner again

* fix note add auto fill

* fix quality

* Revert "fix note add auto fill"

This reverts commit 106e5a8978.

* fix config stuff

* version bump

* fix excess querys

* Fix empty runes and not fake
2025-01-04 10:50:47 -06:00
Kvan7
1afe3ebf0f v0.2.0 (#210)
* Problem with item socets when checking price #207

* [Parse Error] - Translate unique problem #196

* [Fetch Error] - Failed to fetch trade site when using Japanese settings #197

* Choose trade site #171

* consistency fixes

* Add some more preview stuff for rune sockets

* [Parse Error] - Arvil's Wheel Hardwood Targe #145

* version bump
2024-12-30 18:45:29 -06:00
Kvan7
6257a5f257 fix ele damage (#205)
* add missing translate (#200)

* version bump
2024-12-30 06:52:48 -06:00
kvan7
e7f68ffea5 uhh, dont wanna talk about it (v0.1.6)
Some other weapons dont have sockets
2024-12-29 22:19:54 -06:00
Kvan7
c4b84c200d forgot to update readme 2024-12-29 19:50:28 -06:00
Kvan7
5e118f92ea v0.1.5 (#195)
* [Traditional Chinese] Translated and fixed common translation issues (#167)

#163

* Gem Sockets + Anoints (#170)

* adding anoints

* add gem sockets

---------

Co-authored-by: zoedel <6480974+zoedel@users.noreply.github.com>

* Update Japanese (#181)

* add chat parser (#183)

* Adds allocates to parser and pushes data files

* Feature/add Japanese support (#191)

* add ja app_i18n.json

* fix translate

* 表現を統一

* add ja client_strings.js

* add japanese support

* Update poe-dat dep

* Uniques seem to work? (#193)

* Fixes use pseudo incorrect includes

* additional tag for chat parsed items

* Features/sockets (#194)

* init for sockets

* add basic for empty sockets into parser

* Update some socket stuff

* Add some rune data

* add soul cors

* Rune socket ui start

* Working for empty cant disable though

* Empty sockets fully working

* divider

* Add basic dropdown placeholders

* disable select

* Focus search working incorrectly #188

* update README

* Update issue templates

* version bump :)

---------

Co-authored-by: Dr3aming <63219151+Dr3am1ng@users.noreply.github.com>
Co-authored-by: zoedel <zoedel@users.noreply.github.com>
Co-authored-by: zoedel <6480974+zoedel@users.noreply.github.com>
Co-authored-by: Kazuma Igarashi <bonz2go2@gmail.com>
Co-authored-by: Oscar <oscarada87@gmail.com>
2024-12-29 19:47:21 -06:00
Kvan7
835e43a166 v0.1.4 (#166)
* Fix #132

* Features/python script update (#143)

This closes the following issues:

- #67 
- #68 
- #69 
- #70 
- #110 
- #111 
- #112 
- #139

* [Parse Error] - Dualstring bow #60

* Adds starting for localization

* tests yay, this is terrible Add junit tests :( #74

* revert tests cause they were really terrible

* bump gitignore so i dont lose files

* Features/pseudo-pseudo (#93)

* uhhhhhhhhhhhh, maybe pseudo will get support before GGG

* psesudo only ele

* remove git ignored files

* fix ignore

* working tree

* update parser #67

* bump

* more stuff

* maybe working new desc parsing

* Description parser

* Maybe?

* update

* add ko and cmn-Hant

* Add items and pseudo

* push to main

* force utf-16

* moving data

* uh, miss typed a utf 8 for a utf 16

* revert cause that was worse

* Features/moreLocalizationFixes (#148)

* change data files

* ru client strings

* Revert to search by translated name

REVERT THIS LATER ONCE COPY ADVANCED DESC IS BACK

* ru ko and cmn-Hant parse correctly now

* Change Exalted orb to Perfect Jewelers orb

May change again, but just found out about sidekick and since their program was made before creating this, I'm not going to add conflict of logos.

* change from jewelers to div, didn't like how it looked in ui

* Update README

* Update README.md

* Update README.md

* Revert "change from jewelers to div, didn't like how it looked in ui"

This reverts commit b11d33353d.

* Update README.md

* Update localization for PoE2 #66

Functional. Missing some base types in some languages but will make separate issue

* move .ds_store to ds_store (just unhide)

* Builtin browser opens while disabled in settings #103

* [Not Recognized Modifier] - +2 Charm Slots #137

* Remove calls to poeprices for now

* Version Bump to v0.1.4

* Move store and rename

* file move error

* lint + update app_i18n

* [PoE2] Item gets pinned in-game with CTRL+D (auto-hide on) #124

* add fix to pull-json

* final lint fix
2024-12-24 12:45:31 -06:00
Kvan7
fd6c194d39 #130 hotfix, option for pseudo mods 2024-12-20 09:36:17 -06:00
kvan7
81f845df54 Revert "no pseudo"
This reverts commit 78b3ed07e5.
2024-12-19 15:56:35 -06:00
kvan7
78b3ed07e5 no pseudo 2024-12-19 15:52:28 -06:00
Kvan7
c25192e6fe Dev (#128)
* Changes wordig for one vs two hand items (#120)

* [PoE2] - Rune type mod showing as "filters.tag_rune" instead of "RUNE" #108

* [PoE2] Missing Total / Elemental DPS on weapon #109

* [PoE2] no option to change from divine currency to exalt in price check #119

* Adds + to all ele res

* version bump

* also version bump
2024-12-19 15:44:09 -06:00
kvan7
06be6dc2e0 Update faq/common issues 2024-12-18 21:58:35 -06:00
Kvan7
a7009fad05 Update README.md 2024-12-18 16:58:59 -06:00
kvan7
285fcba5ab add back mac and linux to downloads page 2024-12-17 22:31:15 -06:00
kvan7
2f4956f2c7 Merge branch 'master' of github.com:Kvan7/Exiled-Exchange-2 2024-12-17 22:22:22 -06:00
kvan7
256ac61ac0 Update pages site 2024-12-17 22:22:10 -06:00
Kvan7
85bce61525 v0.1.1 (#94)
* [Parse Error] - Dualstring bow #60

* Adds starting for localization

* tests yay, this is terrible Add junit tests :( #74

* revert tests cause they were really terrible

* bump gitignore so i dont lose files

* Update Traditional Chinese (#79)

* Update issue templates

* Update issue templates

* Update for Traditional Chinese

* Update Traditional Chinese

* add new client strings

* Hard coded strings (#80)

* Features/pseudo-pseudo (#93)

* uhhhhhhhhhhhh, maybe pseudo will get support before GGG

* psesudo only ele

* remove git ignored files

* fix ignore

* [Parse Error] - % based value not updating #88

* [Not Recognized Modifier] - Allies in your Presence have +11% to all Elemental Resistances #76

* fully fixes now

* fixes: #61 #75

[Not Recognized Modifier] - Spirit: 100 #61
[Not Recognized Modifier] - Charm Slots: 1 #75

* [Parse Error] - Shouldn't show attacks per second #62

* [Not Recognized Modifier] - Items with bottom help text #63

* version bump
2024-12-17 21:50:25 -06:00
Kvan7
cfc4d6fc1c Update issue templates 2024-12-15 12:58:33 -06:00
Kvan7
6083f56c3b Update issue templates 2024-12-15 12:55:15 -06:00
kvan7
a1b8ab0f75 wrong name 2024-12-14 17:16:54 -06:00
kvan7
448c051bb3 why is it case sensitive 2024-12-14 14:17:52 -06:00
kvan7
aac01ca8c6 bump gh actions 2024-12-14 14:10:49 -06:00
Kvan7
17c66c76c4 Update pages.yml 2024-12-14 14:09:33 -06:00
kvan7
d28b212f63 update and bump for gh actions 2024-12-14 14:07:54 -06:00
kvan7
294ef9d764 update artifact actions? 2024-12-14 13:58:27 -06:00
kvan7
190dacd8a2 try pages working? 2024-12-14 13:53:12 -06:00
Kvan7
d9706290d0 v0.1.0 (#59)
* Features/prettier (#43)

* prettier cause of course

* Adds prettier and item parsing

* lint

* lint 23

* Features/addAnonVodkaPython (#45)

* lots of json

* add width and height update files

* ignore

* update stats

* remove ward and pseudo stats

* Add option to always close overlay (#48)

Fixes #47

I haven't tested this, but it should work fine.

* Features/updateParser (#53)

* logs

* Kinda sorta works?

* switch chaos to ex

* adds flasks and whetstone

* Fix charms

* update phys rune

* add todo

* cleanup

* add pull script

* Trade tag and icons for bulk items (#54)

* Append trade tag and icon for bulk items

* Update items

* update json

* update images & add ref images for later

* bump version

* fix uncut gems

#58
2024-12-14 12:12:33 -06:00
Kvan7
adcd434c71 Update issue templates 2024-12-13 07:11:07 -06:00
Kvan7
1245ef96d6 Fixes error and adds basic price check (#37)
* bump for updating branding

* Bugfix/testing-potential-fix (#31)

* removes the problem

* sdfhgdf

* change to no-referrer-when-downgrade

* Start of fixes for price check (#36)

* feat: Adds new trade web api

Adds types for trade api

* fix lint

* push edits

* Add basic items to items.ndjson

* Added images from old items files

* Fix armor values

* WOOO

* bump version
2024-12-13 00:07:53 -06:00
Kvan7
ab93c8b96a Update issue templates 2024-12-12 21:31:36 -06:00
Kvan7
ab7cb30588 Update issue templates 2024-12-12 21:28:23 -06:00
kvan7
f7d686291f Add release steps to DEVELOPING 2024-12-12 07:10:08 -06:00
kvan7
4b8e2ae8cf bump to prove im not (completely) insane 2024-12-12 06:20:22 -06:00
Kvan7
3450f39381 Update README.md 2024-12-11 21:08:34 -06:00
455 changed files with 155153 additions and 63671 deletions

88
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: Bug Report
description: File a bug report.
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: checkboxes
id: faq
attributes:
label: FAQ
description: Please check the [FAQ](https://kvan7.github.io/Exiled-Exchange-2/faq) before submitting an issue
options:
- label: I have checked the [FAQ](https://kvan7.github.io/Exiled-Exchange-2/faq) and my problem was not there
required: true
- type: checkboxes
id: common-issues
attributes:
label: Common Issues
description: Please check the [Common Issues Page](https://kvan7.github.io/Exiled-Exchange-2/issues) before submitting an issue
options:
- label: I have checked the [Common Issues Page](https://kvan7.github.io/Exiled-Exchange-2/issues) and my problem was not there
required: true
- type: checkboxes
id: existing-issues
attributes:
label: Existing Issues
description: Please check the [pinned issues and existing issues](https://github.com/Kvan7/Exiled-Exchange-2/issues?q=is%3Aissue) before submitting an issue
options:
- label: I have checked the [pinned issues and existing issues](https://github.com/Kvan7/Exiled-Exchange-2/issues?q=is%3Aissue) and my problem was not there
required: true
- type: dropdown
id: common-bug
attributes:
label: Common Bugs
description: Selection for bugs that are known to occur frequently with game updates
options:
- An error occurred while parsing the item
- Not Recognized Modifier
- No modifiers found
- Other
default: 3
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Also what did you expect to happen?
placeholder: Tell us what you see!
value: "Change this text please"
validations:
required: true
- type: dropdown
id: version
attributes:
label: Version
description: What version of EE2 are you running? You can see this in Settings -> About
options:
- 0.13.3
- 0.13.2
- 0.13.1
- 0.13.0
- 0.12.x
- 0.11.x
- 0.10.x
- Change me
default: 5
validations:
required: true
- type: textarea
id: item
attributes:
label: Item Copy text
description: PLEASE copy the item's text and paste it here. You can do so with `ctrl + alt + c` when hovering over the item, if your bug doesn't related to any items at all, write "None" (if it is about an item in any way and you write None it will be ignored)
render: shell
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log output
description: Please go to Settings -> Debug, and copy and paste what is in that text box. (No need for backticks)
render: shell
validations:
required: true

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: true
contact_links:
- name: Feature Request
url: https://github.com/Kvan7/Exiled-Exchange-2/discussions/categories/ideas
about: Feature ideas should be submitted here so they can be discussed before work is done on them
- name: Help/Q&A
url: https://github.com/Kvan7/Exiled-Exchange-2/discussions/categories/q-a
about: Please ask and answer questions here. This is marked as a q&a category so replies that fix things can be marked as "answers" similar to Stack Overflow
- name: Discussions
url: https://github.com/Kvan7/Exiled-Exchange-2/discussions
about: Link to discussions page

View File

@@ -1,24 +0,0 @@
---
name: Something Broken in PoE2
about: Use this for things that worked in PoE 1 and do not in PoE 2
title: "[PoE2]"
labels: bug
assignees: Kvan7
---
**Describe the problem**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem. This is very helpful for comparing the PoE1 vs 2 problems

View File

@@ -34,7 +34,7 @@ jobs:
needs: renderer
strategy:
matrix:
os: [windows-2019] # ubuntu-20.04, macos-14 is a missing runner
os: [windows-2025, ubuntu-22.04, macos-14]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
@@ -59,4 +59,4 @@ jobs:
run: cat ./main/dist/latest-linux.yml
- name: Hash
if: ${{ startsWith(matrix.os, 'macos') }}
run: cat ./main/dist/latest-mac.yml
run: cat ./main/dist/latest-mac.yml

View File

@@ -3,7 +3,7 @@ name: Docs
on:
push:
branches:
- '**'
- 'master'
tags-ignore:
- '**'
paths:
@@ -19,15 +19,15 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm install
working-directory: ./docs
- run: npx vitepress build
working-directory: ./docs
- uses: actions/configure-pages@v2
- uses: actions/upload-pages-artifact@v1
- uses: actions/configure-pages@v4
- uses: actions/upload-pages-artifact@v3
with:
path: ./docs/.vitepress/dist
- id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

23
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
on:
pull_request:
branches:
- master
- dev
jobs:
test-lint-and-format:
name: QA
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/setup-python@v3
- run: npm ci
working-directory: ./renderer
- run: npm ci
working-directory: ./main
- uses: pre-commit/action@v3.0.1
- run: npm run make-index-files
working-directory: ./renderer
- run: npm run test
working-directory: ./renderer

4
.gitignore vendored
View File

@@ -1,4 +1,3 @@
.DS_Store
node_modules
*/dist/**
@@ -19,6 +18,7 @@ yarn-error.log*
*.njsproj
*.sln
*.sw?
.DS_Store
# Electron-builder output
/dist_electron
@@ -26,3 +26,5 @@ yarn-error.log*
# vitepress
docs/.vitepress/dist
docs/.vitepress/cache
*.bin

57
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,57 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: local
hooks:
- id: check-version-consistency
name: Check Version Consistency
entry: python ./versionCheck.py
language: python
files: \.(js|md|yml|json)$
- id: lint-fix-main
name: Lint Fix Main Project
entry: npm run fix --prefix ./main
language: system
pass_filenames: false
files: ^main/.*\.(ts|js|jsx|tsx)$
- id: lint-fix-renderer
name: Lint Fix Renderer Project
entry: npm run lint-fix --prefix ./renderer
language: system
pass_filenames: false
files: ^renderer/.*\.(ts|js|jsx|tsx|vue)$
- id: format-main
name: Format Main Project
entry: npm run format --prefix ./main
language: system
pass_filenames: false
files: ^main/.*\.(ts|js|jsx|tsx|vue|json|md)$
- id: format-renderer
name: Format Renderer Project
entry: npm run format --prefix ./renderer
language: system
pass_filenames: false
files: ^renderer/.*\.(ts|js|jsx|tsx|vue|json|md)$
- id: type-check-main
name: Type Check Main Project
entry: npm run check-types --prefix ./main
language: system
pass_filenames: false
files: ^main/.*\.(ts|js|jsx|tsx|vue|json|md)$
- id: type-check-renderer
name: Type Check Renderer Project
entry: npm run check-types --prefix ./renderer
language: system
pass_filenames: false
files: ^renderer/.*\.(ts|js|jsx|tsx|vue|json|md)$

View File

@@ -27,6 +27,13 @@ npm install
npm run dev
```
## Formatting
```shell
cd renderer
npm run format
```
# How to build
```shell
@@ -36,8 +43,29 @@ npm run make-index-files
npm run build
cd ../main
npm install
npm run build
# We want to sign with a distribution certificate to ensure other users can
# install without errors
CSC_NAME="Certificate name in Keychain" yarn package
CSC_NAME="Certificate name in Keychain" npm run package
```
# How to release a build
1. Commit all changes
2. Bump version in `main/package.json`
3. `npm i` in renderer & main (update `package-lock.json` with new version)
4. `npm run build` in renderer & main
5. Stage & commit bumped version
6. `git push`
7. `git tag vX.X.X`
8. `git push origin vX.X.X`
9. Open release page, create release with tag & title as text of tag & save as draft
# How to build yourself
```shell
sh testUpdate.sh
```
Read the contents of `testUpdate.sh` to understand what it does. Running random scripts from the internet is not recommended so you really should read the code before running it.

View File

@@ -1,22 +1,38 @@
# ![Exalted Orb](./renderer/public/images/exa.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.3.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 commits since latest release (branch)](https://img.shields.io/github/commits-since/kvan7/exiled-exchange-2/latest/dev?style=plastic)
Path of Exile 2 overlay program for price checking items, among many other loved features.
Fork of [Awakened PoE Trade](https://github.com/SnosMe/awakened-poe-trade).
The ONLY official download sites are <https://kvan7.github.io/Exiled-Exchange-2/download> or <https://github.com/Kvan7/Exiled-Exchange-2/releases>, any other locations are not official and may be malicious.
## Moving from POE1/Awakened PoE Trade
1. Download latest release from [releases](https://github.com/Kvan7/exiled-exchange-2/releases)
- Currently only Windows is supported
- Only available as pre-release right now
2. Run installer
3. Copy `apt-data` from `%APPDATA%\awakened-poe-trade` to `%APPDATA%\exiled-exchange-2` to copy your previous settings
3. Run Exiled Exchange 2
4. Launch PoE2 to generate correct files
5. Quit PoE2 and EE2 after seeing the banner popup that EE2 loaded
6. Copy `apt-data` from `%APPDATA%\awakened-poe-trade` to `%APPDATA%\exiled-exchange-2` to copy your previous settings
- Resulting directory structure should look like this:
- `%APPDATA%\exiled-exchange-2\apt-data\`
- `config.json`
4. Run Exiled Exchange 2
7. Edit `config.json` and change the value of "windowTitle": "Path of Exile" to instead be "Path of Exile 2", otherwise it will open only for poe1
8. Start Exiled Exchange 2 and PoE2
## FAQ
<https://kvan7.github.io/Exiled-Exchange-2/faq>
## Tool showcase
| Gem | Rare | Unique | Currency |
| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ |
| ![](https://i.imgur.com/LTsH2DZ.png) | ![](https://i.imgur.com/2XL5Wl8.png) | ![](https://i.imgur.com/UTV6prE.png) | ![](https://i.imgur.com/dQ9Sns6.png) |
| Gem | Rare | Unique | Currency |
| -------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| ![Gem Check](./docs/reference-images/GemCheck.png) | ![Rare Check](./docs/reference-images/RareCheck.png) | ![Unique Check](./docs/reference-images/UniqueCheck.png) | ![Currency Check](./docs/reference-images/CurrencyCheck.png) |
### Development
@@ -30,4 +46,4 @@ See [DEVELOPING.md](./DEVELOPING.md)
- [poeprices.info](https://www.poeprices.info/)
- [poe.ninja](https://poe.ninja/)
![](https://i.imgur.com/MATqhv7.png)
![graph](https://i.imgur.com/MATqhv7.png)

178
dataParser/.gitignore vendored Normal file
View File

@@ -0,0 +1,178 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
test_files_dump/
typings/

View File

@@ -0,0 +1,37 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: mixed-line-ending
- id: check-yaml
- id: check-merge-conflict
- id: check-case-conflict
- id: check-ast
- id: check-builtin-literals
- id: check-docstring-first
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: python-use-type-annotations
- id: python-no-eval
- id: python-check-blanket-noqa
- id: python-check-mock-methods
- id: python-no-log-warn
- repo: https://github.com/PyCQA/bandit
rev: 1.8.3
hooks:
- id: bandit
args: [ "-x", "tests"]
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.6
hooks:
# Run the linter.
- id: ruff
types_or: [ python, pyi ]
args: [ --fix ]
# Run the formatter.
- id: ruff-format
types_or: [ python, pyi ]

15
dataParser/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Data Parser
See [main.py](./src/main.py) for the main entry point. This should be ran from the dataParser folder with the following command:
```bash
python ./src/main.py
```
You can use `--help` to see all options. Running with images will take forever so keep that in mind.
## Developing
I do development in a different private repo with this folder, if it doesn't look up to date let me know and I will try to push changes up to this repo.
If running the whole thing and pushing, you will need to change the `--main-repo-path` option since that has a default for when I run it from the other repo.

9
dataParser/copymain.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
TARGET_DIR="../exiled-exchange-2/dataParser"
rsync -av --exclude-from='.gitignore' --exclude='data/json' --exclude='data/vendor' --exclude="*.json" --exclude='.git' ./ "$TARGET_DIR"
cp ./data/vendor/config.json "$TARGET_DIR/data/vendor"
echo "Files successfully copied to $TARGET_DIR, excluding git-ignored files and 'data' directory."

View File

View File

View File

View File

View File

View File

View File

View File

263
dataParser/data/vendor/config.json vendored Normal file
View File

@@ -0,0 +1,263 @@
{
"steam": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Path of Exile 2",
"files": [
"Metadata/StatDescriptions/active_skill_gem_stat_descriptions.csd",
"Metadata/StatDescriptions/advanced_mod_stat_descriptions.csd",
"Metadata/StatDescriptions/atlas_stat_descriptions.csd",
"Metadata/StatDescriptions/character_panel_gamepad_stat_descriptions.csd",
"Metadata/StatDescriptions/character_panel_stat_descriptions.csd",
"Metadata/StatDescriptions/chest_stat_descriptions.csd",
"Metadata/StatDescriptions/endgame_map_stat_descriptions.csd",
"Metadata/StatDescriptions/expedition_relic_stat_descriptions.csd",
"Metadata/StatDescriptions/gem_stat_descriptions.csd",
"Metadata/StatDescriptions/heist_equipment_stat_descriptions.csd",
"Metadata/StatDescriptions/leaguestone_stat_descriptions.csd",
"Metadata/StatDescriptions/map_stat_descriptions.csd",
"Metadata/StatDescriptions/meta_gem_stat_descriptions.csd",
"Metadata/StatDescriptions/monster_stat_descriptions.csd",
"Metadata/StatDescriptions/passive_skill_aura_stat_descriptions.csd",
"Metadata/StatDescriptions/passive_skill_stat_descriptions.csd",
"Metadata/StatDescriptions/primordial_altar_stat_descriptions.csd",
"Metadata/StatDescriptions/sanctum_relic_stat_descriptions.csd",
"Metadata/StatDescriptions/sentinel_stat_descriptions.csd",
"Metadata/StatDescriptions/skill_stat_descriptions.csd",
"Metadata/StatDescriptions/stat_descriptions.csd",
"Metadata/StatDescriptions/tablet_stat_descriptions.csd",
"Metadata/StatDescriptions/utility_flask_buff_stat_descriptions.csd"
],
"translations": [
"English",
"Russian",
"Korean",
"Traditional Chinese",
"Japanese",
"German",
"Spanish",
"Portuguese"
],
"tables": [
{
"name": "BaseItemTypes",
"columns": [
"Id",
"Name",
"InheritsFrom",
"IsCorrupted",
"Width",
"Height",
"DropLevel",
"Implicit_Mods",
"ItemClass",
"Tags"
]
},
{
"name": "ArmourTypes",
"columns": [
"BaseItemType",
"Armour",
"Evasion",
"EnergyShield",
"IncreasedMovementSpeed",
"Ward"
]
},
{
"name": "ItemClasses",
"columns": [
"Id",
"Name",
"ItemClassCategory"
]
},
{
"name": "WeaponTypes",
"columns": [
"BaseItemType",
"DamageMin",
"DamageMax",
"RangeMax",
"Speed",
"Critical"
]
},
{
"name": "ItemClassCategories",
"columns": [
"Id",
"Text"
]
},
{
"name": "SkillGems",
"columns": [
"BaseItemType",
"IsVaalVariant"
]
},
{
"name": "SkillGemInfo",
"columns": [
"Id",
"Description",
"SkillGemsKey"
]
},
{
"name": "Mods",
"columns": [
"Id",
"Domain",
"Name",
"Stat1",
"Stat2",
"Stat3",
"Stat4",
"Stat5",
"Stat6",
"ModType",
"Stat1Value",
"Stat2Value",
"Stat3Value",
"Stat4Value",
"Stat5Value",
"Stat6Value",
"RadiusJewelType",
"Level",
"GenerationType",
"Families"
]
},
{
"name": "Tags",
"columns": [
"Id",
"DisplayString"
]
},
{
"name": "UniqueStashLayout",
"columns": [
"WordsKey",
"ItemVisualIdentityKey",
"UniqueStashTypesKey",
"RenamedVersion",
"ShowIfEmptyChallengeLeague"
]
},
{
"name": "Words",
"columns": [
"Wordlist",
"Text",
"Text2",
"SpawnWeight_Tags",
"SpawnWeight_Values"
]
},
{
"name": "ItemVisualIdentity",
"columns": [
"Id",
"DDSFile",
"AOFile"
]
},
{
"name": "Stats",
"columns": [
"Id",
"IsLocal",
"IsWeaponLocal",
"Semantic",
"Text",
"IsVirtual",
"HASH32",
"BelongsActiveSkills",
"IsScalable"
]
},
{
"name": "ClientStrings",
"columns": [
"Id",
"Text"
]
},
{
"name": "GoldModPrices",
"columns": [
"Mod",
"Tags",
"SpawnWeight"
]
},
{
"name": "BlightCraftingRecipes",
"columns": [
"Id",
"BlightCraftingResult",
"BlightCraftingItems"
]
},
{
"name": "BlightCraftingItems",
"columns": [
"BaseItemType",
"Tier",
"Achievements",
"UseType",
"NameShort"
]
},
{
"name": "BlightCraftingResults",
"columns": [
"Id",
"Mod",
"PassiveSkill"
]
},
{
"name": "PassiveSkills",
"columns": [
"Id",
"PassiveSkillGraphId",
"Name",
"IsKeystone",
"IsNotable"
]
},
{
"name": "ExpeditionFactions",
"columns": [
"Id",
"Name"
]
},
{
"name": "SoulCores",
"columns": [
"BaseItemType",
"StatsMartialWeapon",
"StatsValuesMartialWeapon",
"StatsArmour",
"StatsValuesArmour",
"RequiredLevel",
"StatsCasterWeapon",
"StatsValuesCasterWeapon",
"StatsAllEquipment",
"StatsValuesAllEquipment"
]
},
{
"name": "SoulCoresPerClass",
"columns": [
"BaseItemType",
"ItemClass",
"Stats",
"StatsValues"
]
}
]
}

0
dataParser/data/vendor/files/.gitkeep vendored Normal file
View File

View File

View File

@@ -0,0 +1,71 @@
{
"folders": [
{
"name": "ee2-data-builder",
"path": "."
},
],
"settings": {
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#48d4f9",
"activityBar.background": "#48d4f9",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#f708c5",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#15202b99",
"sash.hoverBorder": "#48d4f9",
"statusBar.background": "#16c8f8",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#06a9d5",
"statusBarItem.remoteBackground": "#16c8f8",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#16c8f8",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#16c8f899",
"titleBar.inactiveForeground": "#15202b99"
},
"peacock.color": "#16c8f8",
"cSpell.words": [
"Abyssals",
"Amanamu",
"COOLDOWN",
"Cursecarver",
"datalines",
"Defences",
"estazunti",
"Invictus",
"isort",
"Kalguur",
"Kulemak",
"leaguestone",
"Lich",
"Medved",
"METAMORPH",
"Morior",
"Olroth",
"Remembrancing",
"songworthy",
"SYNTHESISED",
"Tecrod",
"Ulaman",
"Vorana",
"Warcries",
"Warstaff",
"Waystone"
],
"python.testing.pytestArgs": [
"tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.analysis.typeCheckingMode": "off",
"python.analysis.autoImportCompletions": true,
"python.envFile": "${workspaceFolder}/.env",
"basedpyright.analysis.ignore": [
"tests/**"
],
"gitlens.virtualRepositories.enabled": false,
"gitlens.plusFeatures.enabled": false,
}
}

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: '中',
RARITY_MAGIC: '魔法',
RARITY_RARE: '稀有',
RARITY_UNIQUE: '傳奇',
RARITY_GEM: '寶石',
RARITY_CURRENCY: '通貨',
RARITY_DIVCARD: '命運卡',
RARITY_QUEST: '任務',
MAP_TIER: '地圖 階級: ',
RARITY: '稀有度: ',
ITEM_CLASS: '物品種類: ',
ITEM_LEVEL: '物品等級: ',
CORPSE_LEVEL: '體 等級: ',
TALISMAN_TIER: '魔符階級: ',
GEM_LEVEL: '等級: ',
STACK_SIZE: '堆疊數量: ',
SOCKETS: '插槽: ',
QUALITY: '品質: ',
PHYSICAL_DAMAGE: '物理傷害: ',
ELEMENTAL_DAMAGE: '元素傷害: ',
LIGHTNING_DAMAGE: '閃電傷害: ',
COLD_DAMAGE: '冰冷傷害: ',
FIRE_DAMAGE: '火焰傷害: ',
CRIT_CHANCE: '暴擊率: ',
ATTACK_SPEED: '每秒攻擊次數: ',
ARMOUR: '護甲值: ',
EVASION: '閃避值: ',
ENERGY_SHIELD: '能量護盾: ',
BLOCK_CHANCE: '格擋機率: ',
CORRUPTED: '已汙染',
UNIDENTIFIED: '未鑑定',
INFLUENCE_SHAPER: '塑者之物',
INFLUENCE_ELDER: '尊師之物',
INFLUENCE_CRUSADER: '聖戰軍王物品',
INFLUENCE_HUNTER: '狩獵者物品',
INFLUENCE_REDEEMER: '救贖者物品',
INFLUENCE_WARLORD: '總督軍物品',
SECTION_SYNTHESISED: '追憶之物',
VEILED_PREFIX: '褻瀆前綴',
VEILED_SUFFIX: '褻瀆後綴',
METAMORPH_HELP: '在塔恩的鍊金室將此與其他四個不同的樣本合成。',
BEAST_HELP: '點擊右鍵將此加入你的獸獵寓言。',
VOIDSTONE_HELP: '放置於此以提升你輿圖全部地圖的階級。',
CANNOT_USE_ITEM: '你無法使用這項裝備,它的數值將被忽略',
AREA_LEVEL: '區域等級: ',
HEIST_WINGS_REVEALED: '已揭露側廂: ',
HEIST_TARGET: '劫盜目標:',
HEIST_BLUEPRINT_ENCHANTS: '附魔裝備',
HEIST_BLUEPRINT_TRINKETS: '盜賊的飾品或通貨',
HEIST_BLUEPRINT_GEMS: '不尋常寶石',
HEIST_BLUEPRINT_REPLICAS: '贗品或實驗性物品',
MIRRORED: '已複製',
PREFIX_MODIFIER: '前綴',
SUFFIX_MODIFIER: '後綴',
CRAFTED_PREFIX: '大師工藝前綴',
CRAFTED_SUFFIX: '大師工藝後綴',
UNSCALABLE_VALUE: ' — 無法使用的值',
CORRUPTED_IMPLICIT: '已汙染固定詞綴',
INCURSION_OPEN: '開啟房間:',
INCURSION_OBSTRUCTED: '受阻的房間:',
ELDRITCH_MOD_R1: '低階',
ELDRITCH_MOD_R2: '高階',
ELDRITCH_MOD_R3: '宏偉',
ELDRITCH_MOD_R4: '卓越',
ELDRITCH_MOD_R5: '精緻',
ELDRITCH_MOD_R6: '完美',
SENTINEL_CHARGE: '充能: ',
FOIL_UNIQUE: '貼模傳奇',
UNMODIFIABLE: '不可調整的',
REQUIREMENTS: '需求',
CHARM_SLOTS: '護符欄位: ',
BASE_SPIRIT: '精魂: ',
QUIVER_HELP_TEXT: '持弓時才可裝備。',
FLASK_HELP_TEXT: '右鍵點擊以喝下藥劑。只有裝備於腰帶上時才會充能。在水井或殺死怪物可回復充能次數。',
CHARM_HELP_TEXT: '達成特定條件時會自動使用。只有裝備於腰帶上時才會充能。可在水井,或是透過擊殺怪物補充。',
PRICE_NOTE: '備註: ',
WAYSTONE_TIER: '換界石階級: ',
WAYSTONE_HELP: '可用於地圖裝置以進入地圖。每個換界石只能被使用一次。',
JEWEL_HELP: '放置到一個天賦樹的珠寶插槽中以產生效果。右鍵點擊以移出插槽。',
SANCTUM_HELP: '將此物品放置到絲克瑪試煉開頭的聖物祭壇。',
TIMELESS_RADIUS: '範圍: ',
PRECURSOR_TABLET_HELP: '可以使用於個人的地圖裝置來增加地圖的詞綴。',
LOGBOOK_HELP: '把此道具帶回你的藏身處給丹尼格,來開啟前往探險的傳送門。',
REQUIRES: '需求: ',
TIMELESS_SMALL_PASSIVES: '範圍內小型天賦也會賦予 {0}',
TIMELESS_NOTABLE_PASSIVES: '範圍內核心天賦也會賦予 {0}',
GRANTS_SKILL: '賦予技能: ',
RELOAD_SPEED: '重新裝填時間: ',
FRACTURED_ITEM: '破裂之物',
SANCTIFIED: '聖化的',
HYPHEN: ' 到 ',
WAYSTONE_REVIVES: '可用的復活數: ',
WAYSTONE_PACK_SIZE: '怪物群大小: ',
WAYSTONE_MAGIC_MONSTERS: '魔法怪物: ',
WAYSTONE_RARE_MONSTERS: '稀有怪物: ',
WAYSTONE_DROP_CHANCE: '換界石掉落機率: ',
WAYSTONE_RARITY: '物品稀有度: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^精良的 (.*)$/,
ITEM_EXCEPTIONAL: /^卓越 (.*)$/,
MAP_BLIGHTED: /^凋落的 (.*)$/,
MAP_BLIGHT_RAVAGED: /^凋落蔓延的 (.*)$/,
ITEM_SYNTHESISED: /^追憶之 (.*)$/,
FLASK_CHARGES: /^目前有 \d+ 充能次數$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^異常的 (.*)$/,
QUALITY_DIVERGENT: /^相異的 (.*)$/,
QUALITY_PHANTASMAL: /^幻影的 (.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(層: (?<tier>\d+)\))?(?:\s+\(級: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^增加 (.*)%$/,
EATER_IMPLICIT: /^吞噬天地固定詞綴 \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^灼烙總督固定詞綴 \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@向 (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@來自 (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'Normal',
RARITY_MAGIC: 'Magisch',
RARITY_RARE: 'Selten',
RARITY_UNIQUE: 'Einzigartig',
RARITY_GEM: 'Gemme',
RARITY_CURRENCY: 'Währung',
RARITY_DIVCARD: 'Weissagungskarte',
RARITY_QUEST: 'Quest',
MAP_TIER: 'Karte Level: ',
RARITY: 'Seltenheit: ',
ITEM_CLASS: 'Gegenstandsklasse: ',
ITEM_LEVEL: 'Gegenstandsstufe: ',
CORPSE_LEVEL: 'Leichnam Stufe: ',
TALISMAN_TIER: 'Talisman-Level: ',
GEM_LEVEL: 'Stufe: ',
STACK_SIZE: 'Stapelgröße: ',
SOCKETS: 'Fassungen: ',
QUALITY: 'Qualität: ',
PHYSICAL_DAMAGE: 'Physischer Schaden: ',
ELEMENTAL_DAMAGE: 'Elementarschaden: ',
LIGHTNING_DAMAGE: 'Blitzschaden: ',
COLD_DAMAGE: 'Kälteschaden: ',
FIRE_DAMAGE: 'Feuerschaden: ',
CRIT_CHANCE: 'Kritische Trefferchance: ',
ATTACK_SPEED: 'Angriffe pro Sekunde: ',
ARMOUR: 'Rüstung: ',
EVASION: 'Ausweichwert: ',
ENERGY_SHIELD: 'Energieschild: ',
BLOCK_CHANCE: 'Blockchance: ',
CORRUPTED: 'Verderbt',
UNIDENTIFIED: 'Nicht identifiziert',
INFLUENCE_SHAPER: 'Schöpfer-Gegenstand',
INFLUENCE_ELDER: 'Ältesten-Gegenstand',
INFLUENCE_CRUSADER: 'Kreuzritter-Gegenstand',
INFLUENCE_HUNTER: 'Jäger-Gegenstand',
INFLUENCE_REDEEMER: 'Erlöserin-Gegenstand',
INFLUENCE_WARLORD: 'Kriegsfürst-Gegenstand',
SECTION_SYNTHESISED: 'Synthetisierter Gegenstand',
VEILED_PREFIX: 'Entweihtes Präfix',
VEILED_SUFFIX: 'Entweihtes Suffix',
METAMORPH_HELP: 'Kombiniere dies mit vier anderen unterschiedlichen Proben in Tanes Laboratorium.',
BEAST_HELP: 'Füge diese Bestie mit Rechtsklick deinem Bestiarium hinzu.',
VOIDSTONE_HELP: 'Fasse dies in deinen Atlas ein, um die Level aller Karten zu erhöhen.',
CANNOT_USE_ITEM: 'Du kannst diesen Gegenstand nicht benutzen. Seine Eigenschaften werden ignoriert.',
AREA_LEVEL: 'Gebietsstufe: ',
HEIST_WINGS_REVEALED: 'Aufgedeckte Gebäudetrakte: ',
HEIST_TARGET: 'Auftragsziel: ',
HEIST_BLUEPRINT_ENCHANTS: 'Verzauberte Rüstung',
HEIST_BLUEPRINT_TRINKETS: 'Dieb-Schmuckstücke oder Währung',
HEIST_BLUEPRINT_GEMS: 'Ungewöhnliche Gemmen',
HEIST_BLUEPRINT_REPLICAS: 'Repliken oder Experimentelle Gegenstände',
MIRRORED: 'Gespiegelt',
PREFIX_MODIFIER: 'Präfix-Modifikator',
SUFFIX_MODIFIER: 'Suffix-Modifikator',
CRAFTED_PREFIX: 'Meister-Präfix-Modifikator',
CRAFTED_SUFFIX: 'Meister-Suffix-Modifikator',
UNSCALABLE_VALUE: ' — Nicht skalierbarer Wert',
CORRUPTED_IMPLICIT: 'Impliziter Modifikator (Verderbtheit)',
INCURSION_OPEN: 'Begehbare Räume:',
INCURSION_OBSTRUCTED: 'Unzugängliche Räume:',
ELDRITCH_MOD_R1: 'Kleine(r)',
ELDRITCH_MOD_R2: 'Größere(r)',
ELDRITCH_MOD_R3: 'Große(r)',
ELDRITCH_MOD_R4: 'Bedeutende(r)',
ELDRITCH_MOD_R5: 'Erlesene(r)',
ELDRITCH_MOD_R6: 'Perfekte(r)',
SENTINEL_CHARGE: 'Ladung: ',
FOIL_UNIQUE: 'Foil-Relikt',
UNMODIFIABLE: 'Nicht modifizierbar',
REQUIREMENTS: 'Anforderungen',
CHARM_SLOTS: 'Phiolen-Plätze: ',
BASE_SPIRIT: 'Wille: ',
QUIVER_HELP_TEXT: 'Kann nur ausgerüstet werden, wenn du einen Bogen trägst.',
FLASK_HELP_TEXT: 'Trinke mit Rechtsklick. Kann nur Füllungen haben, während es sich im Gürtel befindet. Füllt sich auf, wenn du Monster tötest oder Brunnen benutzt.',
CHARM_HELP_TEXT: 'Wird automatisch benutzt, wenn die Bedingung erfüllt ist. Kann nur Füllungen haben, während es sich im Gürtel befindet. Füllt sich auf, wenn du Monster tötest oder Brunnen benutzt.',
PRICE_NOTE: 'Hinweis: ',
WAYSTONE_TIER: 'Wegsteinlevel: ',
WAYSTONE_HELP: 'Kann in einem Kartenapparat verwendet werden und gewährt dir Zutritt zu einer Karte. Wegsteine können nur einmal verwendet werden.',
JEWEL_HELP: 'Platziere das Juwel in einer zugewiesenen Fassung im Fertigkeitenbaum. Mit Rechtsklick kannst du es aus seiner Fassung entfernen.',
SANCTUM_HELP: 'Platziere diesen Gegenstand beim Start jeder Prüfung der Sekhemas auf dem Reliktaltar',
TIMELESS_RADIUS: 'Radius: ',
PRECURSOR_TABLET_HELP: 'Kann in einem persönlichen Kartenapparat verwendet werden, um einer Karte Modifikatoren hinzuzufügen.',
LOGBOOK_HELP: 'Bringt diesen Gegenstand zu Dannig in Eurem Versteck, um Portale zu einer Expedition zu öffnen.',
REQUIRES: 'Erfordert: ',
TIMELESS_SMALL_PASSIVES: 'Kleine Passive Fertigkeiten im Radius gewähren auch {0}',
TIMELESS_NOTABLE_PASSIVES: 'Bedeutende Passive Fertigkeiten im Radius gewähren auch {0}',
GRANTS_SKILL: 'Gewährt Fertigkeit: ',
RELOAD_SPEED: 'Nachladezeit: ',
FRACTURED_ITEM: 'Brüchiger Gegenstand',
SANCTIFIED: 'Geheiligt',
HYPHEN: '-',
WAYSTONE_REVIVES: 'Wiederbelebungen verfügbar: ',
WAYSTONE_PACK_SIZE: 'Monstergruppengröße: ',
WAYSTONE_MAGIC_MONSTERS: 'Magische Monster: ',
WAYSTONE_RARE_MONSTERS: 'Seltene Monster: ',
WAYSTONE_DROP_CHANCE: 'Chance auf fallen gelassene Wegsteine: ',
WAYSTONE_RARITY: 'Gegenstandsseltenheit: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^(.*) \(hochwertig\)$/,
ITEM_EXCEPTIONAL: /^Außergewöhnliches (.*)$/,
MAP_BLIGHTED: /^Befallene (.*)$/,
MAP_BLIGHT_RAVAGED: /^Extrem befallene (.*)$/,
ITEM_SYNTHESISED: /^Synthetisiertes (.*)$/,
FLASK_CHARGES: /^Hat derzeit \d+ Füllungen$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^(.*) \(anormal\)$/,
QUALITY_DIVERGENT: /^(.*) \(abweichend\)$/,
QUALITY_PHANTASMAL: /^(.*) \(illusorisch\)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(Level: (?<tier>\d+)\))?(?:\s+\(Rang: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^.*)% erhöht$/,
EATER_IMPLICIT: /^Impliziter Modifikator des Weltenfressers \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^Impliziter Modifikator des Brennenden Exarchen \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@An (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@Von (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'Normal',
RARITY_MAGIC: 'Magic',
RARITY_RARE: 'Rare',
RARITY_UNIQUE: 'Unique',
RARITY_GEM: 'Gem',
RARITY_CURRENCY: 'Currency',
RARITY_DIVCARD: 'Divination Card',
RARITY_QUEST: 'Quest',
MAP_TIER: 'Map Tier: ',
RARITY: 'Rarity: ',
ITEM_CLASS: 'Item Class: ',
ITEM_LEVEL: 'Item Level: ',
CORPSE_LEVEL: 'Corpse Level: ',
TALISMAN_TIER: 'Talisman Tier: ',
GEM_LEVEL: 'Level: ',
STACK_SIZE: 'Stack Size: ',
SOCKETS: 'Sockets: ',
QUALITY: 'Quality: ',
PHYSICAL_DAMAGE: 'Physical Damage: ',
ELEMENTAL_DAMAGE: 'Elemental Damage: ',
LIGHTNING_DAMAGE: 'Lightning Damage: ',
COLD_DAMAGE: 'Cold Damage: ',
FIRE_DAMAGE: 'Fire Damage: ',
CRIT_CHANCE: 'Critical Hit Chance: ',
ATTACK_SPEED: 'Attacks per Second: ',
ARMOUR: 'Armour: ',
EVASION: 'Evasion Rating: ',
ENERGY_SHIELD: 'Energy Shield: ',
BLOCK_CHANCE: 'Block chance: ',
CORRUPTED: 'Corrupted',
UNIDENTIFIED: 'Unidentified',
INFLUENCE_SHAPER: 'Shaper Item',
INFLUENCE_ELDER: 'Elder Item',
INFLUENCE_CRUSADER: 'Crusader Item',
INFLUENCE_HUNTER: 'Hunter Item',
INFLUENCE_REDEEMER: 'Redeemer Item',
INFLUENCE_WARLORD: 'Warlord Item',
SECTION_SYNTHESISED: 'Synthesised Item',
VEILED_PREFIX: 'Desecrated Prefix',
VEILED_SUFFIX: 'Desecrated Suffix',
METAMORPH_HELP: 'Combine this with four other different samples in Tane\'s Laboratory.',
BEAST_HELP: 'Right-click to add this to your bestiary.',
VOIDSTONE_HELP: 'Socket this into your Atlas to increase the Tier of all Maps.',
CANNOT_USE_ITEM: 'You cannot use this item. Its stats will be ignored',
AREA_LEVEL: 'Area Level: ',
HEIST_WINGS_REVEALED: 'Wings Revealed: ',
HEIST_TARGET: 'Heist Target: ',
HEIST_BLUEPRINT_ENCHANTS: 'Enchanted Armaments',
HEIST_BLUEPRINT_TRINKETS: 'Thieves\' Trinkets or Currency',
HEIST_BLUEPRINT_GEMS: 'Unusual Gems',
HEIST_BLUEPRINT_REPLICAS: 'Replicas or Experimented Items',
MIRRORED: 'Mirrored',
PREFIX_MODIFIER: 'Prefix Modifier',
SUFFIX_MODIFIER: 'Suffix Modifier',
CRAFTED_PREFIX: 'Master Crafted Prefix Modifier',
CRAFTED_SUFFIX: 'Master Crafted Suffix Modifier',
UNSCALABLE_VALUE: ' — Unscalable Value',
CORRUPTED_IMPLICIT: 'Corruption Implicit Modifier',
INCURSION_OPEN: 'Open Rooms:',
INCURSION_OBSTRUCTED: 'Obstructed Rooms:',
ELDRITCH_MOD_R1: 'Lesser',
ELDRITCH_MOD_R2: 'Greater',
ELDRITCH_MOD_R3: 'Grand',
ELDRITCH_MOD_R4: 'Exceptional',
ELDRITCH_MOD_R5: 'Exquisite',
ELDRITCH_MOD_R6: 'Perfect',
SENTINEL_CHARGE: 'Charge: ',
FOIL_UNIQUE: 'Foil Unique',
UNMODIFIABLE: 'Unmodifiable',
REQUIREMENTS: 'Requirements',
CHARM_SLOTS: 'Charm Slots: ',
BASE_SPIRIT: 'Spirit: ',
QUIVER_HELP_TEXT: 'Can only be equipped if you are wielding a Bow.',
FLASK_HELP_TEXT: 'Right click to drink. Can only hold charges while in belt. Refill at Wells or by killing monsters.',
CHARM_HELP_TEXT: 'Used automatically when condition is met. Can only hold charges while in belt. Refill at Wells or by killing monsters.',
PRICE_NOTE: 'Note: ',
WAYSTONE_TIER: 'Waystone Tier: ',
WAYSTONE_HELP: 'Can be used in a Map Device, allowing you to enter a Map. Waystones can only be used once.',
JEWEL_HELP: 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',
SANCTUM_HELP: 'Place this item on the Relic Altar at the start of the Trial of the Sekhemas',
TIMELESS_RADIUS: 'Radius: ',
PRECURSOR_TABLET_HELP: 'Can be used in a personal Map Device to add modifiers to a Map.',
LOGBOOK_HELP: 'Take this item to Dannig in your Hideout to open portals to an expedition.',
REQUIRES: 'Requires: ',
TIMELESS_SMALL_PASSIVES: 'Small Passive Skills in Radius also grant {0}',
TIMELESS_NOTABLE_PASSIVES: 'Notable Passive Skills in Radius also grant {0}',
GRANTS_SKILL: 'Grants Skill: ',
RELOAD_SPEED: 'Reload Time: ',
FRACTURED_ITEM: 'Fractured Item',
SANCTIFIED: 'Sanctified',
HYPHEN: '-',
WAYSTONE_REVIVES: 'Revives Available: ',
WAYSTONE_PACK_SIZE: 'Monster Pack Size: ',
WAYSTONE_MAGIC_MONSTERS: 'Magic Monsters: ',
WAYSTONE_RARE_MONSTERS: 'Rare Monsters: ',
WAYSTONE_DROP_CHANCE: 'Waystone Drop Chance: ',
WAYSTONE_RARITY: 'Item Rarity: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^Superior (.*)$/,
ITEM_EXCEPTIONAL: /^Exceptional (.*)$/,
MAP_BLIGHTED: /^Blighted (.*)$/,
MAP_BLIGHT_RAVAGED: /^Blight-ravaged (.*)$/,
ITEM_SYNTHESISED: /^Synthesised (.*)$/,
FLASK_CHARGES: /^Currently has \d+ Charges$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^Anomalous (.*)$/,
QUALITY_DIVERGENT: /^Divergent (.*)$/,
QUALITY_PHANTASMAL: /^Phantasmal (.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(Tier: (?<tier>\d+)\))?(?:\s+\(Rank: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^(.*)% Increased$/,
EATER_IMPLICIT: /^Eater of Worlds Implicit Modifier \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^Searing Exarch Implicit Modifier \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@To (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@From (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'Normal',
RARITY_MAGIC: 'Mágico',
RARITY_RARE: 'Raro',
RARITY_UNIQUE: 'Único',
RARITY_GEM: 'Gema',
RARITY_CURRENCY: 'Objetos monetarios',
RARITY_DIVCARD: 'Carta de adivinación',
RARITY_QUEST: 'Misión',
MAP_TIER: 'Mapa Grado: ',
RARITY: 'Rareza: ',
ITEM_CLASS: 'Clase de objeto: ',
ITEM_LEVEL: 'Nivel de objeto: ',
CORPSE_LEVEL: 'Cadáver Nivel: ',
TALISMAN_TIER: 'Grado de talismán: ',
GEM_LEVEL: 'Nivel: ',
STACK_SIZE: 'Tamaño de la pila: ',
SOCKETS: 'Engarces: ',
QUALITY: 'Calidad: ',
PHYSICAL_DAMAGE: 'Daño físico: ',
ELEMENTAL_DAMAGE: 'Daño elemental: ',
LIGHTNING_DAMAGE: 'Daño de rayo: ',
COLD_DAMAGE: 'Daño de hielo: ',
FIRE_DAMAGE: 'Daño de fuego: ',
CRIT_CHANCE: 'Probabilidad de impacto crítico: ',
ATTACK_SPEED: 'Ataques por segundo: ',
ARMOUR: 'Armadura: ',
EVASION: 'Evasión: ',
ENERGY_SHIELD: 'Escudo de energía: ',
BLOCK_CHANCE: 'Probabilidad de bloqueo: ',
CORRUPTED: 'Corrupto',
UNIDENTIFIED: 'Sin identificar',
INFLUENCE_SHAPER: 'Objeto del Creador',
INFLUENCE_ELDER: 'Objeto del Antiguo',
INFLUENCE_CRUSADER: 'Objeto del Cruzado',
INFLUENCE_HUNTER: 'Objeto del Cazador',
INFLUENCE_REDEEMER: 'Objeto de la Redentora',
INFLUENCE_WARLORD: 'Objeto del Jefe de guerra',
SECTION_SYNTHESISED: 'Objeto Sintetizado',
VEILED_PREFIX: 'Prefijo profanado',
VEILED_SUFFIX: 'Sufijo profanado',
METAMORPH_HELP: 'Combina esto con otras cuatro muestras distintas en el laboratorio de Tane.',
BEAST_HELP: 'Haz clic derecho para añadirla a tu Bestiario.',
VOIDSTONE_HELP: 'Colócala en tu Atlas para aumentar el grado de todos los mapas.',
CANNOT_USE_ITEM: 'No puedes usar este objeto. Sus estadísticas serán ignoradas',
AREA_LEVEL: 'Nivel del área: ',
HEIST_WINGS_REVEALED: 'Alas reveladas: ',
HEIST_TARGET: 'Objetivo del atraco: ',
HEIST_BLUEPRINT_ENCHANTS: 'Armamentos encantados',
HEIST_BLUEPRINT_TRINKETS: 'Abalorios de ladrones u objetos monetarios',
HEIST_BLUEPRINT_GEMS: 'Gemas inusuales',
HEIST_BLUEPRINT_REPLICAS: 'Réplicas u objetos experimentales',
MIRRORED: 'Reflejado',
PREFIX_MODIFIER: 'Mod. de prefijo',
SUFFIX_MODIFIER: 'Mod. de sufijo',
CRAFTED_PREFIX: 'Mod. de prefijo fabricado con maestros',
CRAFTED_SUFFIX: 'Mod. de sufijo fabricado con maestros',
UNSCALABLE_VALUE: ' — Valor fijo',
CORRUPTED_IMPLICIT: 'Mod. implícito de corrupción',
INCURSION_OPEN: 'Salas abiertas:',
INCURSION_OBSTRUCTED: 'Salas bloqueadas:',
ELDRITCH_MOD_R1: 'menor',
ELDRITCH_MOD_R2: 'mayor',
ELDRITCH_MOD_R3: 'grandioso',
ELDRITCH_MOD_R4: 'excepcional',
ELDRITCH_MOD_R5: 'exquisito',
ELDRITCH_MOD_R6: 'perfecto',
SENTINEL_CHARGE: 'Energía: ',
FOIL_UNIQUE: 'Único brillante',
UNMODIFIABLE: 'No se puede modificar',
REQUIREMENTS: 'Requisitos',
CHARM_SLOTS: 'Espacios para vial: ',
BASE_SPIRIT: 'Espíritu: ',
QUIVER_HELP_TEXT: 'Solo se puede equipar si portas un arco.',
FLASK_HELP_TEXT: 'Haz clic derecho para beber. Solo puede mantener sus cargas mientras está en el cinturón. Se rellena en los pozos o al matar monstruos.',
CHARM_HELP_TEXT: 'Se usa automáticamente cuando cumples una determinada condición. Solo puede mantener sus cargas mientras está en el cinturón. Se rellena en los pozos o matando monstruos.',
PRICE_NOTE: 'Nota: ',
WAYSTONE_TIER: 'Grado de la piedra guía: ',
WAYSTONE_HELP: 'Se puede colocar en un artefacto de mapas para entrar en un mapa. Las rocas guía solo se pueden usar una vez.',
JEWEL_HELP: 'Colócala en un engarce de joya del Árbol de habilidades pasivas. Haz clic derecho para retirarla del engarce.',
SANCTUM_HELP: 'Coloca este objeto en el Altar de reliquias que se encuentra en la entrada de la Prueba de las sekhemas',
TIMELESS_RADIUS: 'Radio: ',
PRECURSOR_TABLET_HELP: 'Se puede usar en un artefacto de mapas personal para agregar modificadores a un mapa.',
LOGBOOK_HELP: 'Llévale este objeto a Dannig en tu guarida para abrir portales hacia una expedición.',
REQUIRES: 'Requiere: ',
TIMELESS_SMALL_PASSIVES: 'Las habilidades pasivas pequeñas dentro del radio también otorgan {0}',
TIMELESS_NOTABLE_PASSIVES: 'Las habilidades pasivas notables dentro del radio también otorgan {0}',
GRANTS_SKILL: 'Otorga la habilidad: ',
RELOAD_SPEED: 'Tiempo de recarga: ',
FRACTURED_ITEM: 'Objeto fracturado',
SANCTIFIED: 'Santificado',
HYPHEN: '-',
WAYSTONE_REVIVES: 'Resurrecciones disponibles: ',
WAYSTONE_PACK_SIZE: 'Tamaño de los grupos de monstruos: ',
WAYSTONE_MAGIC_MONSTERS: 'Monstruos mágicos: ',
WAYSTONE_RARE_MONSTERS: 'Monstruos raros: ',
WAYSTONE_DROP_CHANCE: 'Probabilidad de botín de piedra guía: ',
WAYSTONE_RARITY: 'Rareza de objetos: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^(.*) Superior$/,
ITEM_EXCEPTIONAL: /^(.*) excepcional$/,
MAP_BLIGHTED: /^(.*) infestado $/,
MAP_BLIGHT_RAVAGED: /^(.*) devastado por la plaga$/,
ITEM_SYNTHESISED: /^(.*) Sintetizado$/,
FLASK_CHARGES: /^Actualmente tiene \d+ cargas$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^(.*) anómala$/,
QUALITY_DIVERGENT: /^(.*) divergente$/,
QUALITY_PHANTASMAL: /^(.*) fantasmal$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(Grado: (?<tier>\d+)\))?(?:\s+\(Rango: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^Aumentado un (.*)%$/,
EATER_IMPLICIT: /^Mod. implícito del Devorador de mundos \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^Mod. implícito del Exarca abrasador \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@Para (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@De (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,157 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'ノーマル',
RARITY_MAGIC: 'マジック',
RARITY_RARE: 'レア',
RARITY_UNIQUE: 'ユニーク',
RARITY_GEM: 'ジェム',
RARITY_CURRENCY: 'カレンシー',
RARITY_DIVCARD: '占いカード',
RARITY_QUEST: 'クエスト',
MAP_TIER: 'マップ ティア: ',
RARITY: 'レアリティ: ',
ITEM_CLASS: 'アイテムクラス: ',
ITEM_LEVEL: 'アイテムレベル: ',
CORPSE_LEVEL: '死体 レベル: ',
TALISMAN_TIER: 'タリスマンティア: ',
GEM_LEVEL: 'レベル: ',
STACK_SIZE: 'スタック数: ',
SOCKETS: 'ソケット: ',
QUALITY: '品質: ',
PHYSICAL_DAMAGE: '物理ダメージ: ',
ELEMENTAL_DAMAGE: '元素ダメージ: ',
LIGHTNING_DAMAGE: '雷ダメージ: ',
COLD_DAMAGE: '冷気ダメージ: ',
FIRE_DAMAGE: '火ダメージ: ',
CRIT_CHANCE: 'クリティカルヒット率: ',
ATTACK_SPEED: '秒間アタック回数: ',
ARMOUR: 'アーマー: ',
EVASION: '回避力: ',
ENERGY_SHIELD: 'エナジーシールド: ',
BLOCK_CHANCE: 'ブロック率: ',
CORRUPTED: 'コラプト状態',
UNIDENTIFIED: '未鑑定',
INFLUENCE_SHAPER: 'シェイパーアイテム',
INFLUENCE_ELDER: 'エルダーアイテム',
INFLUENCE_CRUSADER: 'クルセイダーアイテム',
INFLUENCE_HUNTER: 'ハンターアイテム',
INFLUENCE_REDEEMER: 'リディーマーアイテム',
INFLUENCE_WARLORD: 'ウォーロードアイテム',
SECTION_SYNTHESISED: 'シンセシスアイテム',
VEILED_PREFIX: '冒涜プレフィックス',
VEILED_SUFFIX: '冒涜サフィックス',
METAMORPH_HELP: 'ターネの研究所でこのサンプルと別箇所のサンプル4つを組み合わせる。',
BEAST_HELP: '右クリックしてこのモンスターを怪獣園に追加する。',
VOIDSTONE_HELP: 'これをアトラスにはめて、
すべてのマップのティアを上げる。',
CANNOT_USE_ITEM: 'このアイテムを使用できません。アイテムの効果は無視されます',
AREA_LEVEL: 'エリアレベル: ',
HEIST_WINGS_REVEALED: '情報を聞いた区画: ',
HEIST_TARGET: 'ハイスト目標: ',
HEIST_BLUEPRINT_ENCHANTS: 'エンチャントされた武具',
HEIST_BLUEPRINT_TRINKETS: '盗賊のトリンケットまたはカレンシー',
HEIST_BLUEPRINT_GEMS: '異常なジェム',
HEIST_BLUEPRINT_REPLICAS: 'レプリカまたはエクスペリメントアイテム',
MIRRORED: 'ミラー状態',
PREFIX_MODIFIER: 'プレフィックスモッド',
SUFFIX_MODIFIER: 'サフィックスモッド',
CRAFTED_PREFIX: 'マスターがクラフトしたプレフィックスモッド',
CRAFTED_SUFFIX: 'マスターがクラフトしたサフィックスモッド',
UNSCALABLE_VALUE: ' — スケールできない値',
CORRUPTED_IMPLICIT: 'コラプト暗黙モッド',
INCURSION_OPEN: '開放された部屋:',
INCURSION_OBSTRUCTED: '塞がれた部屋:',
ELDRITCH_MOD_R1: 'レッサー',
ELDRITCH_MOD_R2: 'グレーター',
ELDRITCH_MOD_R3: 'グランド',
ELDRITCH_MOD_R4: 'エクセプショナル',
ELDRITCH_MOD_R5: 'エクスクイジット',
ELDRITCH_MOD_R6: 'パーフェクト',
SENTINEL_CHARGE: 'チャージ: ',
FOIL_UNIQUE: 'フォイルユニーク',
UNMODIFIABLE: '変更不可',
REQUIREMENTS: '装備要求',
CHARM_SLOTS: 'チャームスロット: ',
BASE_SPIRIT: 'スピリット: ',
QUIVER_HELP_TEXT: '弓装備時のみ装備可能',
FLASK_HELP_TEXT: '右クリックして飲む。腰につけているときだけチャージを貯めることができる。井戸で、またはモンスターを倒すことで補充される。',
CHARM_HELP_TEXT: '条件を満たした時に自動的に使用される。腰につけているときだけチャージを貯めることができる。井戸で、またはモンスターを倒すことで補充される。',
PRICE_NOTE: 'メモ: ',
WAYSTONE_TIER: 'ウェイストーンティア: ',
WAYSTONE_HELP: 'マップデバイスで使用すると、マップに入ることができる。ウェイストーンは1度のみ使用できる。',
JEWEL_HELP: 'パッシブツリーで割り当てられたジュエルソケットにはめる。右クリックしてソケットから取り外すことができる。',
SANCTUM_HELP: 'セケマの試練開始時にこのアイテムをレリックの祭壇に置く',
TIMELESS_RADIUS: '半径: ',
PRECURSOR_TABLET_HELP: '自身のマップデバイスで使用してマップにモッドを追加できる。',
LOGBOOK_HELP: 'このアイテムをダニグに渡し、自身の隠れ家でエクスペディションへのポータルを開く。',
REQUIRES: '装備条件:',
TIMELESS_SMALL_PASSIVES: '範囲内のスモールパッシブスキルは{0}も付与する',
TIMELESS_NOTABLE_PASSIVES: '範囲内のノータブルパッシブスキルは{0}も付与する',
GRANTS_SKILL: 'スキルを付与: ',
RELOAD_SPEED: '再装填時間: ',
FRACTURED_ITEM: 'フラクチャーアイテム',
SANCTIFIED: '聖別化',
HYPHEN: '-',
WAYSTONE_REVIVES: '復活が利用可能: ',
WAYSTONE_PACK_SIZE: 'モンスターパックサイズ: ',
WAYSTONE_MAGIC_MONSTERS: 'マジックモンスター: ',
WAYSTONE_RARE_MONSTERS: 'レアモンスター: ',
WAYSTONE_DROP_CHANCE: 'ウェイストーンドロップ確率: ',
WAYSTONE_RARITY: 'アイテムレアリティ: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^上質な (.*)$/,
ITEM_EXCEPTIONAL: /^規格外の (.*)$/,
MAP_BLIGHTED: /^ブライトに覆われた(.*)$/,
MAP_BLIGHT_RAVAGED: /^ブライトに破壊された(.*)$/,
ITEM_SYNTHESISED: /^シンセサイズされた (.*)$/,
FLASK_CHARGES: /^現在\d+チャージ$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^異常な(.*)$/,
QUALITY_DIVERGENT: /^相違の(.*)$/,
QUALITY_PHANTASMAL: /^幻想の(.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(ティア: (?<tier>\d+)\))?(?:\s+\(ランク: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^.*)%増加$/,
EATER_IMPLICIT: /^世界を喰らう者 暗黙モッド 「(?<rank>.+)」$/,
EXARCH_IMPLICIT: /^灼熱の代行者 暗黙モッド 「(?<rank>.+)」$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@宛先 (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@差出人 (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: '일반',
RARITY_MAGIC: '마법',
RARITY_RARE: '희귀',
RARITY_UNIQUE: '고유',
RARITY_GEM: '젬',
RARITY_CURRENCY: '화폐',
RARITY_DIVCARD: '점술 카드',
RARITY_QUEST: '퀘스트',
MAP_TIER: '지도 등급: ',
RARITY: '아이템 희귀도: ',
ITEM_CLASS: '아이템 종류: ',
ITEM_LEVEL: '아이템 레벨: ',
CORPSE_LEVEL: '시신 레벨: ',
TALISMAN_TIER: '부적 등급: ',
GEM_LEVEL: '레벨: ',
STACK_SIZE: '중첩 개수: ',
SOCKETS: '홈: ',
QUALITY: '퀄리티: ',
PHYSICAL_DAMAGE: '물리 피해: ',
ELEMENTAL_DAMAGE: '원소 피해: ',
LIGHTNING_DAMAGE: '번개 피해: ',
COLD_DAMAGE: '냉기 피해: ',
FIRE_DAMAGE: '화염 피해: ',
CRIT_CHANCE: '치명타 명중 확률: ',
ATTACK_SPEED: '초당 공격 횟수: ',
ARMOUR: '방어도: ',
EVASION: '회피: ',
ENERGY_SHIELD: '에너지 보호막: ',
BLOCK_CHANCE: '막기 확률: ',
CORRUPTED: '타락',
UNIDENTIFIED: '미확인',
INFLUENCE_SHAPER: '쉐이퍼 아이템',
INFLUENCE_ELDER: '엘더 아이템',
INFLUENCE_CRUSADER: '십자군 아이템',
INFLUENCE_HUNTER: '사냥꾼 아이템',
INFLUENCE_REDEEMER: '대속자 아이템',
INFLUENCE_WARLORD: '전쟁군주 아이템',
SECTION_SYNTHESISED: '결합된 아이템',
VEILED_PREFIX: '훼손된 접두어',
VEILED_SUFFIX: '훼손된 접미어',
METAMORPH_HELP: '테인의 연구실에서 이 아이템을 다른 샘플과 조합하십시오.',
BEAST_HELP: '우클릭으로 이것을 야수 도감에 추가하십시오.',
VOIDSTONE_HELP: '아틀라스에 장착하여 모든 지도의 등급을 상승시키십시오.',
CANNOT_USE_ITEM: '아이템 착용 불가. 아이템 효과 미적용',
AREA_LEVEL: '지역 레벨: ',
HEIST_WINGS_REVEALED: '부속 건물 드러남: ',
HEIST_TARGET: '강탈 대상: ',
HEIST_BLUEPRINT_ENCHANTS: '인챈트된 병기',
HEIST_BLUEPRINT_TRINKETS: '도둑의 장신구 또는 화폐',
HEIST_BLUEPRINT_GEMS: '특이한 젬',
HEIST_BLUEPRINT_REPLICAS: '모조품 또는 실험적 아이템',
MIRRORED: '복제',
PREFIX_MODIFIER: '접두어 속성 부여',
SUFFIX_MODIFIER: '접미어 속성 부여',
CRAFTED_PREFIX: '대가의 제작 접두어 속성 부여',
CRAFTED_SUFFIX: '대가의 제작 접미어 속성 부여',
UNSCALABLE_VALUE: ' — 변경이 불가능한 값',
CORRUPTED_IMPLICIT: '타락 고정 속성 부여',
INCURSION_OPEN: '열린 방:',
INCURSION_OBSTRUCTED: '막힌 방:',
ELDRITCH_MOD_R1: '하급',
ELDRITCH_MOD_R2: '상급',
ELDRITCH_MOD_R3: '우수한',
ELDRITCH_MOD_R4: '특출난',
ELDRITCH_MOD_R5: '정교한',
ELDRITCH_MOD_R6: '완벽한',
SENTINEL_CHARGE: '충전: ',
FOIL_UNIQUE: '반짝이 버전 고유 아이템',
UNMODIFIABLE: '속성 부여 불가',
REQUIREMENTS: '요구사항',
CHARM_SLOTS: '호신부 슬롯: ',
BASE_SPIRIT: '정신력: ',
QUIVER_HELP_TEXT: '활 장착 시에만 장착할 수 있습니다.',
FLASK_HELP_TEXT: '마시려면 우클릭하십시오. 허리띠에 장착 중일 때만 충전이 유지됩니다. 우물에서 충전하거나 몬스터를 처치해서 충전할 수 있습니다.',
CHARM_HELP_TEXT: '조건이 충족되면 자동으로 사용됩니다. 허리띠에 장착 중일 때만 충전이 유지됩니다. 우물에서 충전하거나 몬스터를 처치해서 충전할 수 있습니다.',
PRICE_NOTE: '메모: ',
WAYSTONE_TIER: '경로석 등급: ',
WAYSTONE_HELP: '지도 장치에서 사용하면 지도 안으로 이동할 수 있습니다. 경로석은 한 번만 사용할 수 있습니다.',
JEWEL_HELP: '패시브 스킬 트리에서 포인트를 할당한 주얼 슬롯에 장착하십시오. 우클릭하면 슬롯에서 제거됩니다.',
SANCTUM_HELP: '세케마의 시련을 시작할 때 유물 제단에 이 아이템을 놓으십시오',
TIMELESS_RADIUS: '적용 반경: ',
PRECURSOR_TABLET_HELP: '전용 지도 장치에서 지도에 속성을 부여할 수 있습니다.',
LOGBOOK_HELP: '이 아이템을 은신처의 대닉에게 가져가면 탐험 포탈을 열 수 있습니다.',
REQUIRES: '요구 사항: ',
TIMELESS_SMALL_PASSIVES: '반경 내 소형 패시브 스킬이 {0}도 부여',
TIMELESS_NOTABLE_PASSIVES: '반경 내 주요 패시브 스킬이 {0}도 부여',
GRANTS_SKILL: '스킬 부여: ',
RELOAD_SPEED: '재장전 시간: ',
FRACTURED_ITEM: '분열된 아이템',
SANCTIFIED: '축성',
HYPHEN: '-',
WAYSTONE_REVIVES: '부활 횟수: ',
WAYSTONE_PACK_SIZE: '몬스터 무리 규모: ',
WAYSTONE_MAGIC_MONSTERS: '마법 몬스터: ',
WAYSTONE_RARE_MONSTERS: '희귀 몬스터: ',
WAYSTONE_DROP_CHANCE: '경로석 출현 확률: ',
WAYSTONE_RARITY: '아이템 희귀도: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^상급 (.*)$/,
ITEM_EXCEPTIONAL: /^특출난 (.*)$/,
MAP_BLIGHTED: /^역병 걸린 (.*)$/,
MAP_BLIGHT_RAVAGED: /^역병에 유린당한 (.*)$/,
ITEM_SYNTHESISED: /^결합된 (.*)$/,
FLASK_CHARGES: /^현재 충전량: \d+$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^기묘한 (.*)$/,
QUALITY_DIVERGENT: /^상이한 (.*)$/,
QUALITY_PHANTASMAL: /^몽환적인 (.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(등급: (?<tier>\d+)\))?(?:\s+\(단계: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^(.*)% 증가$/,
EATER_IMPLICIT: /^세계 포식자 고정 속성 \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^작열의 총주교 고정 속성 \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@발신 (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@수신 (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'Normal',
RARITY_MAGIC: 'Mágico',
RARITY_RARE: 'Raro',
RARITY_UNIQUE: 'Único',
RARITY_GEM: 'Gema',
RARITY_CURRENCY: 'Moeda',
RARITY_DIVCARD: 'Carta de Adivinhação',
RARITY_QUEST: 'Missão',
MAP_TIER: 'Mapa Tier: ',
RARITY: 'Raridade: ',
ITEM_CLASS: 'Classe do Item: ',
ITEM_LEVEL: 'Nível do Item: ',
CORPSE_LEVEL: 'Cadáver Nível: ',
TALISMAN_TIER: 'Tier do Talismã: ',
GEM_LEVEL: 'Nível: ',
STACK_SIZE: 'Tamanho da Pilha: ',
SOCKETS: 'Encaixes: ',
QUALITY: 'Qualidade: ',
PHYSICAL_DAMAGE: 'Dano Físico: ',
ELEMENTAL_DAMAGE: 'Dano Elemental: ',
LIGHTNING_DAMAGE: 'Dano de Raio: ',
COLD_DAMAGE: 'Dano de Gelo: ',
FIRE_DAMAGE: 'Dano de Fogo: ',
CRIT_CHANCE: 'Chance de Acerto Crítico: ',
ATTACK_SPEED: 'Ataques por Segundo: ',
ARMOUR: 'Armadura: ',
EVASION: 'Evasão: ',
ENERGY_SHIELD: 'Escudo de Energia: ',
BLOCK_CHANCE: 'Chance de Bloqueio: ',
CORRUPTED: 'Corrompido',
UNIDENTIFIED: 'Não Identificado',
INFLUENCE_SHAPER: 'Item do Criador',
INFLUENCE_ELDER: 'Item do Ancião',
INFLUENCE_CRUSADER: 'Item do Cruzado',
INFLUENCE_HUNTER: 'Item do Caçador',
INFLUENCE_REDEEMER: 'Item do Redentor',
INFLUENCE_WARLORD: 'Item do Senhor da Guerra',
SECTION_SYNTHESISED: 'Item Sintetizado',
VEILED_PREFIX: 'Prefixo Profanado',
VEILED_SUFFIX: 'Sufixo Profanado',
METAMORPH_HELP: 'Combine este com quatro outras amostras diferentes no Laboratório de Tane.',
BEAST_HELP: 'Clique com o botão direito para adicionar isto ao seu bestiário.',
VOIDSTONE_HELP: 'Encaixe isso no seu Atlas para aumentar o Tier de todos os Mapas.',
CANNOT_USE_ITEM: 'Você não pode utilizar este item. Suas propriedades serão ignoradas',
AREA_LEVEL: 'Nível da Área: ',
HEIST_WINGS_REVEALED: 'Alas Reveladas: ',
HEIST_TARGET: 'Alvo do Golpe: ',
HEIST_BLUEPRINT_ENCHANTS: 'Armamentos Encantados',
HEIST_BLUEPRINT_TRINKETS: 'Adornos de Ladrão ou Itens Monetários',
HEIST_BLUEPRINT_GEMS: 'Gemas Diferentes',
HEIST_BLUEPRINT_REPLICAS: 'Réplicas ou Itens Experimentais',
MIRRORED: 'Espelhado',
PREFIX_MODIFIER: 'Mod Prefixo',
SUFFIX_MODIFIER: 'Mod Sufixo',
CRAFTED_PREFIX: 'Mod Prefixo Criado por Mestre',
CRAFTED_SUFFIX: 'Mod Sufixo Criado por Mestre',
UNSCALABLE_VALUE: ' — Valor não escalável',
CORRUPTED_IMPLICIT: 'Mod Implícito Corrompido',
INCURSION_OPEN: 'Salas Abertas:',
INCURSION_OBSTRUCTED: 'Salas Obstruídas:',
ELDRITCH_MOD_R1: 'Menor',
ELDRITCH_MOD_R2: 'Maior',
ELDRITCH_MOD_R3: 'Distinto',
ELDRITCH_MOD_R4: 'Excepcional',
ELDRITCH_MOD_R5: 'Requintado',
ELDRITCH_MOD_R6: 'Perfeito',
SENTINEL_CHARGE: 'Carga: ',
FOIL_UNIQUE: 'Único Laminado',
UNMODIFIABLE: 'Não Modificável',
REQUIREMENTS: 'Requisitos',
CHARM_SLOTS: 'Espaço de Patuás: ',
BASE_SPIRIT: 'Espírito: ',
QUIVER_HELP_TEXT: 'Só pode ser equipado se você estiver usando um Arco.',
FLASK_HELP_TEXT: 'Clique com o botão direito para beber. Só pode armazenar cargas enquanto estiver no cinto. Reabasteça em Poços ou matando monstros.',
CHARM_HELP_TEXT: 'Usado automaticamente quando a condição é atendida. Só pode armazenar cargas enquanto estiver no cinto. Reabasteça em Poços ou matando monstros.',
PRICE_NOTE: 'Nota: ',
WAYSTONE_TIER: 'Tier da Pedra Guia: ',
WAYSTONE_HELP: 'Pode ser usado em um Dispositivo de Mapa, permitindo que você entre em um Mapa. Dreno de Mana só podem ser usadas uma vez.',
JEWEL_HELP: 'Coloque-a em um Encaixe de Joias alocado na Árvore de Habilidades Passivas. Clique com o botão direito para removê-la do Encaixe.',
SANCTUM_HELP: 'Coloque este item no Altar de Relíquias no início da Provação das Sekhemas',
TIMELESS_RADIUS: 'Raio: ',
PRECURSOR_TABLET_HELP: 'Pode ser usado em um Dispositivo de Mapas pessoal para adicionar modificadores em um Mapa.',
LOGBOOK_HELP: 'Leve este item até Dannig em seu Refúgio para abrir portais para uma expedição.',
REQUIRES: 'Requer: ',
TIMELESS_SMALL_PASSIVES: 'Habilidades Passivas Pequenas no Raio também concedem {0} ',
TIMELESS_NOTABLE_PASSIVES: 'Habilidades Passivas Notáveis no Raio também concedem {0}',
GRANTS_SKILL: 'Concede Habilidade: ',
RELOAD_SPEED: 'Tempo de Recarregamento: ',
FRACTURED_ITEM: 'Item Fixado',
SANCTIFIED: 'Santificado',
HYPHEN: '-',
WAYSTONE_REVIVES: 'Ressurreições: ',
WAYSTONE_PACK_SIZE: 'Tamanho do Grupo de Monstros: ',
WAYSTONE_MAGIC_MONSTERS: 'Monstros Mágicos: ',
WAYSTONE_RARE_MONSTERS: 'Monstros Raros: ',
WAYSTONE_DROP_CHANCE: 'Chance de Queda de Pedra Guia: ',
WAYSTONE_RARITY: 'Raridade de Itens: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^(.*) Superior$/,
ITEM_EXCEPTIONAL: /^(.*) Excepcional$/,
MAP_BLIGHTED: /^(.*) Infestado$/,
MAP_BLIGHT_RAVAGED: /^(.*) Devastado$/,
ITEM_SYNTHESISED: /^(.*) Sintetizado$/,
FLASK_CHARGES: /^Atualmente tem \d+ Cargas$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^Anômalo (.*)$/,
QUALITY_DIVERGENT: /^Divergente (.*)$/,
QUALITY_PHANTASMAL: /^Fantasmal (.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(ier: (?<tier>\d+)\))?(?:\s+\(Rank: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^(.*)% Aumentado$/,
EATER_IMPLICIT: /^Modificador Implícito do Devorador de Mundos (?<rank>.+)$/,
EXARCH_IMPLICIT: /^Modificador Implícito do Exarca Cauterizado (?<rank>.+)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@Para (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@De (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,156 @@
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
RARITY_NORMAL: 'Обычный',
RARITY_MAGIC: 'Волшебный',
RARITY_RARE: 'Редкий',
RARITY_UNIQUE: 'Уникальный',
RARITY_GEM: 'Камень',
RARITY_CURRENCY: 'Валюта',
RARITY_DIVCARD: 'Гадальная карта',
RARITY_QUEST: 'Задание',
MAP_TIER: 'Карта Ранг: ',
RARITY: 'Редкость: ',
ITEM_CLASS: 'Класс предмета: ',
ITEM_LEVEL: 'Уровень предмета: ',
CORPSE_LEVEL: 'Труп Уровень: ',
TALISMAN_TIER: 'Уровень талисмана: ',
GEM_LEVEL: 'Уровень: ',
STACK_SIZE: 'Размер стопки: ',
SOCKETS: 'Гнезда: ',
QUALITY: 'Качество: ',
PHYSICAL_DAMAGE: 'Физический урон: ',
ELEMENTAL_DAMAGE: 'Урон от стихий: ',
LIGHTNING_DAMAGE: 'Урон от молнии: ',
COLD_DAMAGE: 'Урон от холода: ',
FIRE_DAMAGE: 'Урон от огня: ',
CRIT_CHANCE: 'Шанс крит. попадания: ',
ATTACK_SPEED: 'Атак в секунду: ',
ARMOUR: 'Броня: ',
EVASION: 'Уклонение: ',
ENERGY_SHIELD: 'Энергетический щит: ',
BLOCK_CHANCE: 'Шанс блока: ',
CORRUPTED: 'Осквернено ',
UNIDENTIFIED: 'Неопознано',
INFLUENCE_SHAPER: 'Предмет Создателя',
INFLUENCE_ELDER: 'Древний предмет',
INFLUENCE_CRUSADER: 'Предмет Крестоносца',
INFLUENCE_HUNTER: 'Предмет Охотника',
INFLUENCE_REDEEMER: 'Предмет Избавительницы',
INFLUENCE_WARLORD: 'Предмет Вождя',
SECTION_SYNTHESISED: 'Синтезированный предмет',
VEILED_PREFIX: 'Очернённый префикс',
VEILED_SUFFIX: 'Очернённый суффикс',
METAMORPH_HELP: 'Объедините эту часть с четырьмя другими в Лаборатории Танэ.',
BEAST_HELP: 'Нажмите ПКМ, чтобы добавить это в ваш Бестиарий.',
VOIDSTONE_HELP: 'Поместите этот предмет в ваш Атлас, чтобы повысить уровень всех карт.',
CANNOT_USE_ITEM: 'Вы не можете использовать этот предмет, его параметры не будут учтены',
AREA_LEVEL: 'Уровень области: ',
HEIST_WINGS_REVEALED: 'Крыльев обнаружено: ',
HEIST_TARGET: 'Предмет кражи: ',
HEIST_BLUEPRINT_ENCHANTS: 'Зачарованное вооружение',
HEIST_BLUEPRINT_TRINKETS: 'Воровские украшения или валюта',
HEIST_BLUEPRINT_GEMS: 'Необычные камни',
HEIST_BLUEPRINT_REPLICAS: 'Копии или экспериментальные предметы',
MIRRORED: 'Отражено',
PREFIX_MODIFIER: 'Префикс',
SUFFIX_MODIFIER: 'Суффикс',
CRAFTED_PREFIX: 'Мастерский префикс',
CRAFTED_SUFFIX: 'Мастерский суффикс',
UNSCALABLE_VALUE: ' — Неизменяемое значение',
CORRUPTED_IMPLICIT: 'Осквернённое собственное свойство',
INCURSION_OPEN: 'Открытые комнаты:',
INCURSION_OBSTRUCTED: 'Отделённые комнаты:',
ELDRITCH_MOD_R1: 'Мелкий',
ELDRITCH_MOD_R2: 'Крупный',
ELDRITCH_MOD_R3: 'Великий',
ELDRITCH_MOD_R4: 'Превосходный',
ELDRITCH_MOD_R5: 'Первоклассный',
ELDRITCH_MOD_R6: 'Безупречный',
SENTINEL_CHARGE: 'Заряд: ',
FOIL_UNIQUE: 'Особый уникальный предмет',
UNMODIFIABLE: 'Неизменяемый',
REQUIREMENTS: 'Требования',
CHARM_SLOTS: 'Ячейки оберегов: ',
BASE_SPIRIT: 'Дух: ',
QUIVER_HELP_TEXT: 'Можно использовать только с луком в руках.',
FLASK_HELP_TEXT: 'Щёлкните ПКМ, чтобы выпить. Содержит заряды только когда висит на поясе. Пополняется у колодцев или по мере убийства монстров.',
CHARM_HELP_TEXT: 'Используется автоматически при определённых условиях. Содержит заряды только когда висит на поясе. Пополняется у колодцев или по мере убийства монстров.',
PRICE_NOTE: 'Примечание: ',
WAYSTONE_TIER: 'Уровень путевого камня: ',
WAYSTONE_HELP: 'Можно использовать в Машине картоходца, чтобы войти на карту. Путевые камни одноразовые.',
JEWEL_HELP: 'Поместите в доступное гнездо для самоцветов на дереве пассивных умений. Чтобы вынуть из гнезда, щёлкните по нему правой кнопкой мыши.',
SANCTUM_HELP: 'Разместите этот предмет на алтаре реликвий перед началом Испытания Сехем',
TIMELESS_RADIUS: 'Радиус: ',
PRECURSOR_TABLET_HELP: 'Можно использовать в личной Машине картоходца для добавления свойств на карту.',
LOGBOOK_HELP: 'Отнесите этот предмет Дэннигу в вашем убежище, чтобы открыть порталы в экспедицию.',
REQUIRES: 'Требуется: ',
TIMELESS_SMALL_PASSIVES: 'Малые пассивные умения в радиусе также дают: {0}',
TIMELESS_NOTABLE_PASSIVES: 'Значимые пассивные умения в радиусе также дают: {0}',
GRANTS_SKILL: 'Дарует умение: ',
RELOAD_SPEED: 'Время перезарядки: ',
FRACTURED_ITEM: 'Расколотый предмет',
SANCTIFIED: 'Освящено',
HYPHEN: '-',
WAYSTONE_REVIVES: 'Доступно возрождений: ',
WAYSTONE_PACK_SIZE: 'Размер групп монстров: ',
WAYSTONE_MAGIC_MONSTERS: 'Волшебные монстры: ',
WAYSTONE_RARE_MONSTERS: 'Редкие монстры: ',
WAYSTONE_DROP_CHANCE: 'Шанс выпадения путевого камня: ',
WAYSTONE_RARITY: 'Редкость предметов: ',
// [Array]
SHAPER_MODS: ['of Shaping', 'The Shaper\'s'],
// [Array]
ELDER_MODS: ['of the Elder', 'The Elder\'s'],
// [Array]
CRUSADER_MODS: ['Crusader\'s', 'of the Crusade'],
// [Array]
HUNTER_MODS: ['Hunter\'s', 'of the Hunt'],
// [Array]
REDEEMER_MODS: ['Redeemer\'s', 'of Redemption'],
// [Array]
WARLORD_MODS: ['Warlord\'s', 'of the Conquest'],
// [Array]
DELVE_MODS: ['Subterranean', 'of the Underground'],
// [Array]
VEILED_MODS: ['Chosen', 'of the Order'],
// [Array]
INCURSION_MODS: ['Guatelitzi\'s', 'Xopec\'s', 'Topotante\'s', 'Tacati\'s', 'Matatl\'s', 'of Matatl', 'Citaqualotl\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],
ITEM_SUPERIOR: /^(.*) высокого качества$/,
ITEM_EXCEPTIONAL: /^Образцовое (.*)$/,
MAP_BLIGHTED: /^Заражённая (.*)$/,
MAP_BLIGHT_RAVAGED: /^Разорённая Скверной (.*)$/,
ITEM_SYNTHESISED: /^Синтезированное (.*) $/,
FLASK_CHARGES: /^Содержит зарядов: \d+$/,
// [Manual]
METAMORPH_BRAIN: /^.* Brain$/,
// [Manual]
METAMORPH_EYE: /^.* Eye$/,
// [Manual]
METAMORPH_LUNG: /^.* Lung$/,
// [Manual]
METAMORPH_HEART: /^.* Heart$/,
// [Manual]
METAMORPH_LIVER: /^.* Liver$/,
QUALITY_ANOMALOUS: /^Аномальный: (.*)$/,
QUALITY_DIVERGENT: /^Искривлённый: (.*)$/,
QUALITY_PHANTASMAL: /^Фантомный: (.*)$/,
MODIFIER_LINE: /^(?<type>[^"]+)(?:\s+"(?<name>[^"]*)")?(?:\s+\(Уровень: (?<tier>\d+)\))?(?:\s+\(Ранг: (?<rank>\d+)\))?$/,
MODIFIER_INCREASED: /^(.*)% увеличение$/,
EATER_IMPLICIT: /^Собственное свойство Пожирателя миров \((?<rank>.+)\)$/,
EXARCH_IMPLICIT: /^Собственное свойство Пламенного экзарха \((?<rank>.+)\)$/,
// [Manual]
CHAT_SYSTEM: /^: (?<body>.+)$/,
// [Manual]
CHAT_TRADE: /^\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GLOBAL: /^#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_TO: /^@Кому (?<char_name>.+?): (?<body>.+)$/,
CHAT_WHISPER_FROM: /^@От кого (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,
// [Manual]
CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\d+) (?<gem_qual>\d+)% (?<gem_name>.+)$/,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
[tool.pytest.ini_options]
pythonpath = [".", "src"]

View File

@@ -0,0 +1,328 @@
from collections.abc import Sequence
from typing import Callable
from contracts.models.base_client_string import BaseClientString
from contracts.models.regex_client_string import RegexClientString
from models.array_client_string import ArrayClientString
from models.capture_placeholder_client_string import CapturePlaceholderClientString
from models.client_string import ClientString
from models.const_client_string import ConstClientString
from models.const_regex_client_string import ConstRegexClientString
CLIENT_STRING_JS_HEADER = """
// @ts-check
/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */
export default {
"""
CLIENT_STRING_JS_FOOTER = """
}
"""
def on_character(char: str, secondary_char: str | None = None) -> Callable[[str], int]:
def inner(value: str) -> int:
try:
return value.index(char)
except ValueError:
if secondary_char is None:
raise
return value.index(secondary_char)
return inner
def modifier_type(value: str) -> int:
return value.replace("\\", "").index("{") - 1
CLIENT_STRING_KEY_VALUES: Sequence[BaseClientString] = [
ClientString("RARITY_NORMAL", ["ItemDisplayStringNormal"]),
ClientString("RARITY_MAGIC", ["ItemDisplayStringMagic"]),
ClientString("RARITY_RARE", ["ItemDisplayStringRare"]),
ClientString("RARITY_UNIQUE", ["ItemDisplayStringUnique"]),
ClientString("RARITY_GEM", ["ItemDisplayStringGem"]),
ClientString("RARITY_CURRENCY", ["ItemDisplayStringCurrency"]),
ClientString("RARITY_DIVCARD", ["ItemDisplayStringDivinationCard"]),
ClientString("RARITY_QUEST", ["ItemDisplayStringQuest"]),
ClientString("MAP_TIER", ["UIOptionsSectionTitleMap", "Tier"], "{0} {1}: "),
ClientString("RARITY", ["ItemDisplayStringRarity"], "{0}: "),
ClientString("ITEM_CLASS", ["ItemDisplayStringClass"], "{0}: "),
ClientString("ITEM_LEVEL", ["ItemDisplayStringItemLevel"], "{0}: "),
ClientString(
"CORPSE_LEVEL",
["CorpseSuffix", "Level"],
"{0} {1}: ",
[(2, -1), None],
),
ClientString("TALISMAN_TIER", ["ItemDisplayStringTalismanTier"], "{0}: "),
ClientString("GEM_LEVEL", ["Level"], "{0}: "),
ClientString("STACK_SIZE", ["ItemDisplayStackSize"], "{0}: "),
ClientString("SOCKETS", ["ItemDisplayStringSockets"], "{0}: "),
ClientString("QUALITY", ["Quality"], "{0}: "),
ClientString("PHYSICAL_DAMAGE", ["ItemDisplayWeaponPhysicalDamage"], "{0}: "),
ClientString("ELEMENTAL_DAMAGE", ["ItemDisplayWeaponElementalDamage"], "{0}: "),
ClientString("LIGHTNING_DAMAGE", ["ItemDisplayWeaponLightningDamage"], "{0}: "),
ClientString("COLD_DAMAGE", ["ItemDisplayWeaponColdDamage"], "{0}: "),
ClientString("FIRE_DAMAGE", ["ItemDisplayWeaponFireDamage"], "{0}: "),
ClientString("CRIT_CHANCE", ["ItemDisplayWeaponCriticalStrikeChance"], "{0}: "),
ClientString("ATTACK_SPEED", ["ItemDisplayWeaponAttacksPerSecond"], "{0}: "),
ClientString("ARMOUR", ["ItemDisplayArmourArmour"], "{0}: "),
ClientString("EVASION", ["ItemDisplayArmourEvasionRating"], "{0}: "),
ClientString("ENERGY_SHIELD", ["ItemDisplayArmourEnergyShield"], "{0}: "),
ClientString("BLOCK_CHANCE", ["ItemDisplayShieldBlockChance"], "{0}: "),
ClientString("CORRUPTED", ["ItemPopupCorrupted"]),
ClientString("UNIDENTIFIED", ["ItemPopupUnidentified"]),
# skip regex ones
ClientString("INFLUENCE_SHAPER", ["ItemPopupShaperItem"]),
ClientString("INFLUENCE_ELDER", ["ItemPopupElderItem"]),
ClientString("INFLUENCE_CRUSADER", ["ItemPopupCrusaderItem"]),
ClientString("INFLUENCE_HUNTER", ["ItemPopupHunterItem"]),
ClientString("INFLUENCE_REDEEMER", ["ItemPopupRedeemerItem"]),
ClientString("INFLUENCE_WARLORD", ["ItemPopupWarlordItem"]),
ClientString("SECTION_SYNTHESISED", ["ItemPopupSynthesisedItem"]),
# skip regex
ClientString("VEILED_PREFIX", ["ItemDisplayVeiledPrefix"]),
ClientString("VEILED_SUFFIX", ["ItemDisplayVeiledSuffix"]),
# regex
ClientString("METAMORPH_HELP", ["MetamorphosisItemisedMapBoss"]),
ClientString("BEAST_HELP", ["ItemDescriptionItemisedCapturedMonster"]),
ClientString("VOIDSTONE_HELP", ["PrimordialWatchstoneDescriptionText"]),
# regex (metamorph)
ClientString("CANNOT_USE_ITEM", ["ItemPopupCannotUseItem"]),
# regex (gems)
ClientString("AREA_LEVEL", ["ItemDisplayHeistContractLevel"], "{0}: "),
ClientString("HEIST_WINGS_REVEALED", ["ItemDisplayHeistBlueprintWings"], "{0}: "),
ClientString("HEIST_TARGET", ["ItemDisplayHeistContractObjective"]),
ClientString("HEIST_BLUEPRINT_ENCHANTS", ["HeistBlueprintRewardBunker"]),
ClientString("HEIST_BLUEPRINT_TRINKETS", ["HeistBlueprintRewardMines"]),
ClientString("HEIST_BLUEPRINT_GEMS", ["HeistBlueprintRewardReliquary"]),
ClientString("HEIST_BLUEPRINT_REPLICAS", ["HeistBlueprintRewardLibrary"]),
ClientString("MIRRORED", ["ItemPopupMirrored"]),
# regex (mod description)
ClientString(
"PREFIX_MODIFIER",
["ModDescriptionLinePrefix"],
substring=[(0, modifier_type)],
keep_format_option=True,
trim=True,
),
ClientString(
"SUFFIX_MODIFIER",
["ModDescriptionLineSuffix"],
substring=[(0, modifier_type)],
keep_format_option=True,
trim=True,
),
ClientString(
"CRAFTED_PREFIX",
["ModDescriptionLineCraftedPrefix"],
substring=[(0, modifier_type)],
keep_format_option=True,
trim=True,
),
ClientString(
"CRAFTED_SUFFIX",
["ModDescriptionLineCraftedSuffix"],
substring=[(0, modifier_type)],
keep_format_option=True,
trim=True,
),
ClientString(
"UNSCALABLE_VALUE",
["DescriptionLabelFixedValueStat"],
substring=[(28, on_character("}"))],
keep_format_option=True,
),
ClientString("CORRUPTED_IMPLICIT", ["ModDescriptionLineCorruptedImplicit"]),
ClientString("INCURSION_OPEN", ["ItemDescriptionIncursionAccessibleRooms"]),
ClientString("INCURSION_OBSTRUCTED", ["ItemDescriptionIncursionInaccessibleRooms"]),
# regex eater exarch implicit
ClientString("ELDRITCH_MOD_R1", ["EldritchCurrencyTier1"]),
ClientString("ELDRITCH_MOD_R2", ["EldritchCurrencyTier2"]),
ClientString("ELDRITCH_MOD_R3", ["EldritchCurrencyTier3"]),
ClientString("ELDRITCH_MOD_R4", ["EldritchCurrencyTier4"]),
ClientString("ELDRITCH_MOD_R5", ["EldritchCurrencyTier5"]),
ClientString("ELDRITCH_MOD_R6", ["EldritchCurrencyTier6"]),
ClientString("SENTINEL_CHARGE", ["ItemDisplaySentinelDroneDurability"], "{0}: "),
# Arrays for influence mod prefix suffixes
ClientString(
"FOIL_UNIQUE",
["ItemPopupFoilUniqueVariant"],
substring=[(0, on_character(" ("))],
trim=True,
),
ClientString("UNMODIFIABLE", ["ItemPopupUnmodifiable"]),
# regex (chat)
ClientString("REQUIREMENTS", ["ItemPopupRequirements"]),
ClientString("CHARM_SLOTS", ["ItemDisplayBeltCharmSlots"], "{0}: "),
ClientString("BASE_SPIRIT", ["ItemDisplaySpiritValue"], "{0}: "),
ClientString("QUIVER_HELP_TEXT", ["ItemDescriptionQuiver"]),
ClientString("FLASK_HELP_TEXT", ["ItemDescriptionFlask"]),
ClientString("CHARM_HELP_TEXT", ["ItemDescriptionFlaskUtility1"]),
ClientString("PRICE_NOTE", ["ItemDisplayStringNote"], "{0}: "),
ClientString("WAYSTONE_TIER", ["ItemDisplayMapTier"], "{0}: "),
ClientString("WAYSTONE_HELP", ["ItemDescriptionMap"]),
ClientString("JEWEL_HELP", ["ItemDescriptionPassiveJewel"]),
ClientString("SANCTUM_HELP", ["ItemDescriptionSanctumRelic"]),
ClientString("TIMELESS_RADIUS", ["JewelRadiusLabel"], "{0}: "),
ClientString("PRECURSOR_TABLET_HELP", ["ItemDescriptionPrecursorTablet"]),
ClientString("LOGBOOK_HELP", ["ItemDescriptionExpeditionLogbook"]),
ClientString("REQUIRES", ["ItemRequirementsLabel"]),
ClientString(
"TIMELESS_SMALL_PASSIVES",
["ModStatJewelAddToSmall"],
keep_format_option=True,
),
ClientString(
"TIMELESS_NOTABLE_PASSIVES",
["ModStatJewelAddToNotable"],
keep_format_option=True,
),
ClientString("GRANTS_SKILL", ["ItemDisplayGrantsSkill"], "{0}: "),
ClientString("RELOAD_SPEED", ["SkillPopupReloadTime"], "{0}: "),
ClientString("FRACTURED_ITEM", ["ItemPopupFracturedItem"]),
ClientString("SANCTIFIED", ["ItemPopupSanctified"]),
ClientString("HYPHEN", ["ItemDisplayWeaponDamageRange"]),
ClientString("WAYSTONE_REVIVES", ["NumberOfPortalsPerWaystone"], "{0}: "),
ClientString("WAYSTONE_PACK_SIZE", ["ItemDisplayMapPackSizeIncrease"], "{0}: "),
ClientString(
"WAYSTONE_MAGIC_MONSTERS", ["ItemDisplayMapMagicMonsterQuantityBonus"], "{0}: "
),
ClientString(
"WAYSTONE_RARE_MONSTERS", ["ItemDisplayMapRareMonsterQuantityBonus"], "{0}: "
),
ClientString(
"WAYSTONE_DROP_CHANCE", ["ItemDisplayMapMapItemDropChanceIncrease"], "{0}: "
),
ClientString("WAYSTONE_RARITY", ["ItemDisplayMapRarityIncrease"], "{0}: "),
]
CLIENT_STRING_ARRAYS: list[ArrayClientString] = [
ArrayClientString(
[
ConstClientString("SHAPER_MODS", "of Shaping"),
ConstClientString("SHAPER_MODS", "The Shaper's"),
]
),
ArrayClientString(
[
ConstClientString("ELDER_MODS", "of the Elder"),
ConstClientString("ELDER_MODS", "The Elder's"),
]
),
ArrayClientString(
[
ConstClientString("CRUSADER_MODS", "Crusader's"),
ConstClientString("CRUSADER_MODS", "of the Crusade"),
]
),
ArrayClientString(
[
ConstClientString("HUNTER_MODS", "Hunter's"),
ConstClientString("HUNTER_MODS", "of the Hunt"),
]
),
ArrayClientString(
[
ConstClientString("REDEEMER_MODS", "Redeemer's"),
ConstClientString("REDEEMER_MODS", "of Redemption"),
]
),
ArrayClientString(
[
ConstClientString("WARLORD_MODS", "Warlord's"),
ConstClientString("WARLORD_MODS", "of the Conquest"),
]
),
ArrayClientString(
[
ConstClientString("DELVE_MODS", "Subterranean"),
ConstClientString("DELVE_MODS", "of the Underground"),
]
),
ArrayClientString(
[
ConstClientString("VEILED_MODS", "Chosen"),
ConstClientString("VEILED_MODS", "of the Order"),
]
),
ArrayClientString(
[
ConstClientString("INCURSION_MODS", "Guatelitzi's"),
ConstClientString("INCURSION_MODS", "Xopec's"),
ConstClientString("INCURSION_MODS", "Topotante's"),
ConstClientString("INCURSION_MODS", "Tacati's"),
ConstClientString("INCURSION_MODS", "Matatl's"),
ConstClientString("INCURSION_MODS", "of Matatl"),
ConstClientString("INCURSION_MODS", "Citaqualotl's"),
ConstClientString("INCURSION_MODS", "of Citaqualotl"),
ConstClientString("INCURSION_MODS", "of Tacati"),
ConstClientString("INCURSION_MODS", "of Guatelitzi"),
ConstClientString("INCURSION_MODS", "of Puhuarte"),
]
),
]
CLIENT_STRING_REGEX: list[RegexClientString] = [
CapturePlaceholderClientString("ITEM_SUPERIOR", ["QualityItem"]),
CapturePlaceholderClientString("ITEM_EXCEPTIONAL", ["ExceptionalItem"]),
CapturePlaceholderClientString("MAP_BLIGHTED", ["InfectedMap"]),
CapturePlaceholderClientString("MAP_BLIGHT_RAVAGED", ["UberInfectedMap"]),
CapturePlaceholderClientString("ITEM_SYNTHESISED", ["SynthesisedItem"]),
CapturePlaceholderClientString(
"FLASK_CHARGES", ["ItemDisplayChargesNCharges"], capture_str="\\\\d+"
),
ConstRegexClientString("METAMORPH_BRAIN", ".* Brain"),
ConstRegexClientString("METAMORPH_EYE", ".* Eye"),
ConstRegexClientString("METAMORPH_LUNG", ".* Lung"),
ConstRegexClientString("METAMORPH_HEART", ".* Heart"),
ConstRegexClientString("METAMORPH_LIVER", ".* Liver"),
CapturePlaceholderClientString("QUALITY_ANOMALOUS", ["GemAlternateQuality1Affix"]),
CapturePlaceholderClientString("QUALITY_DIVERGENT", ["GemAlternateQuality2Affix"]),
CapturePlaceholderClientString("QUALITY_PHANTASMAL", ["GemAlternateQuality3Affix"]),
CapturePlaceholderClientString(
"MODIFIER_LINE",
["ModDescriptionLineTier", "ModDescriptionLineRank"],
'(?<type>[^"]+)(?:\\s+"(?<name>[^"]*)")?(?:\\s+\\({0}: (?<tier>\\d+)\\))?(?:\\s+\\({1}: (?<rank>\\d+)\\))?',
[(3, on_character(":", "")), (3, on_character(":", ""))],
),
CapturePlaceholderClientString(
"MODIFIER_INCREASED",
["AlternateQualityModIncreaseText"],
substring=[(3, None)],
),
CapturePlaceholderClientString(
"EATER_IMPLICIT",
["ModDescriptionLineGreatTangleImplicit"],
capture_str="(?<rank>.+)",
),
CapturePlaceholderClientString(
"EXARCH_IMPLICIT",
["ModDescriptionLineCleansingFireImplicit"],
capture_str="(?<rank>.+)",
),
ConstRegexClientString("CHAT_SYSTEM", ": (?<body>.+)"),
ConstRegexClientString(
"CHAT_TRADE", "\\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)"
),
ConstRegexClientString(
"CHAT_GLOBAL", "#(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)"
),
ConstRegexClientString(
"CHAT_PARTY", "%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)"
),
ConstRegexClientString(
"CHAT_GUILD", "&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)"
),
CapturePlaceholderClientString(
"CHAT_WHISPER_TO", ["ChatBoxTo"], "@{0} (?<char_name>.+?): (?<body>.+)"
),
CapturePlaceholderClientString(
"CHAT_WHISPER_FROM",
["ChatBoxFrom"],
"@{0} (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)",
),
ConstRegexClientString(
"CHAT_WEBTRADE_GEM", "level (?<gem_lvl>\\d+) (?<gem_qual>\\d+)% (?<gem_name>.+)"
),
]

View File

@@ -0,0 +1,217 @@
"""Names of various files that are generated/constant"""
from typing import Literal
# Trade API files
ITEMS_JSON = "items.json"
FILTERS_JSON = "filters.json"
STATS_JSON = "stats.json"
STATIC_JSON = "static.json"
TRADE_API_FILES = (
ITEMS_JSON,
FILTERS_JSON,
STATS_JSON,
STATIC_JSON,
)
# Game API tables
GAME_API_TABLES_TYPE = Literal[
"ArmourTypes.json",
"BaseItemTypes.json",
"BlightCraftingItems.json",
"BlightCraftingRecipes.json",
"BlightCraftingResults.json",
"ClientStrings.json",
"ExpeditionFactions.json",
"GoldModPrices.json",
"ItemClassCategories.json",
"ItemClasses.json",
"ItemVisualIdentity.json",
"Mods.json",
"PassiveSkills.json",
"SkillGemInfo.json",
"SkillGems.json",
"SoulCores.json",
"SoulCoresPerClass.json",
"Stats.json",
"Tags.json",
"UniqueStashLayout.json",
"WeaponTypes.json",
"Words.json",
]
ARMOUR_TYPES = "ArmourTypes.json"
BASE_ITEM_TYPES = "BaseItemTypes.json"
BLIGHT_CRAFTING_ITEMS = "BlightCraftingItems.json"
BLIGHT_CRAFTING_RECIPES = "BlightCraftingRecipes.json"
BLIGHT_CRAFTING_RESULTS = "BlightCraftingResults.json"
CLIENT_STRINGS = "ClientStrings.json"
EXPEDITION_FACTIONS = "ExpeditionFactions.json"
GOLD_MOD_PRICES = "GoldModPrices.json"
ITEM_CLASS_CATEGORIES = "ItemClassCategories.json"
ITEM_CLASSES = "ItemClasses.json"
ITEM_VISUAL_IDENTITY = "ItemVisualIdentity.json"
MODS = "Mods.json"
PASSIVE_SKILLS = "PassiveSkills.json"
SKILL_GEM_INFO = "SkillGemInfo.json"
SKILL_GEMS = "SkillGems.json"
SOUL_CORES = "SoulCores.json"
SOUL_CORES_PER_CLASS = "SoulCoresPerClass.json"
STATS = "Stats.json"
TAGS = "Tags.json"
UNIQUE_STASH_LAYOUT = "UniqueStashLayout.json"
WEAPON_TYPES = "WeaponTypes.json"
WORDS = "Words.json"
GAME_API_TABLES = (
ARMOUR_TYPES,
BASE_ITEM_TYPES,
BLIGHT_CRAFTING_ITEMS,
BLIGHT_CRAFTING_RECIPES,
BLIGHT_CRAFTING_RESULTS,
CLIENT_STRINGS,
EXPEDITION_FACTIONS,
GOLD_MOD_PRICES,
ITEM_CLASS_CATEGORIES,
ITEM_CLASSES,
ITEM_VISUAL_IDENTITY,
MODS,
PASSIVE_SKILLS,
SKILL_GEM_INFO,
SKILL_GEMS,
SOUL_CORES,
SOUL_CORES_PER_CLASS,
STATS,
TAGS,
UNIQUE_STASH_LAYOUT,
WEAPON_TYPES,
WORDS,
)
# Stat Description Files
STAT_DESCRIPTION_FILES_TYPE = Literal[
"Metadata@StatDescriptions@active_skill_gem_stat_descriptions.csd",
"Metadata@StatDescriptions@advanced_mod_stat_descriptions.csd",
"Metadata@StatDescriptions@atlas_stat_descriptions.csd",
"Metadata@StatDescriptions@character_panel_gamepad_stat_descriptions.csd",
"Metadata@StatDescriptions@character_panel_stat_descriptions.csd",
"Metadata@StatDescriptions@chest_stat_descriptions.csd",
"Metadata@StatDescriptions@endgame_map_stat_descriptions.csd",
"Metadata@StatDescriptions@expedition_relic_stat_descriptions.csd",
"Metadata@StatDescriptions@gem_stat_descriptions.csd",
"Metadata@StatDescriptions@heist_equipment_stat_descriptions.csd",
"Metadata@StatDescriptions@leaguestone_stat_descriptions.csd",
"Metadata@StatDescriptions@map_stat_descriptions.csd",
"Metadata@StatDescriptions@meta_gem_stat_descriptions.csd",
"Metadata@StatDescriptions@monster_stat_descriptions.csd",
"Metadata@StatDescriptions@passive_skill_aura_stat_descriptions.csd",
"Metadata@StatDescriptions@passive_skill_stat_descriptions.csd",
"Metadata@StatDescriptions@primordial_altar_stat_descriptions.csd",
"Metadata@StatDescriptions@sanctum_relic_stat_descriptions.csd",
"Metadata@StatDescriptions@sentinel_stat_descriptions.csd",
"Metadata@StatDescriptions@skill_stat_descriptions.csd",
"Metadata@StatDescriptions@stat_descriptions.csd",
"Metadata@StatDescriptions@tablet_stat_descriptions.csd",
"Metadata@StatDescriptions@utility_flask_buff_stat_descriptions.csd",
]
ACTIVE_SKILL_GEM_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@active_skill_gem_stat_descriptions.csd"
)
ADVANCED_MOD_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@advanced_mod_stat_descriptions.csd"
)
ATLAS_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@atlas_stat_descriptions.csd"
)
CHARACTER_PANEL_GAMEPAD_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@character_panel_gamepad_stat_descriptions.csd"
)
CHARACTER_PANEL_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@character_panel_stat_descriptions.csd"
)
CHEST_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@chest_stat_descriptions.csd"
)
ENDGAME_MAP_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@endgame_map_stat_descriptions.csd"
)
EXPEDITION_RELIC_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@expedition_relic_stat_descriptions.csd"
)
GEM_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@gem_stat_descriptions.csd"
)
HEIST_EQUIPMENT_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@heist_equipment_stat_descriptions.csd"
)
LEAGUESTONE_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@leaguestone_stat_descriptions.csd"
)
MAP_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@map_stat_descriptions.csd"
)
META_GEM_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@meta_gem_stat_descriptions.csd"
)
MONSTER_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@monster_stat_descriptions.csd"
)
PASSIVE_SKILL_AURA_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@passive_skill_aura_stat_descriptions.csd"
)
PASSIVE_SKILL_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@passive_skill_stat_descriptions.csd"
)
PRIMORDIAL_ALTAR_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@primordial_altar_stat_descriptions.csd"
)
SANCTUM_RELIC_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@sanctum_relic_stat_descriptions.csd"
)
SENTINEL_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@sentinel_stat_descriptions.csd"
)
SKILL_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@skill_stat_descriptions.csd"
)
STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@stat_descriptions.csd"
)
TABLET_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@tablet_stat_descriptions.csd"
)
UTILITY_FLASK_BUFF_STAT_DESCRIPTIONS: STAT_DESCRIPTION_FILES_TYPE = (
"Metadata@StatDescriptions@utility_flask_buff_stat_descriptions.csd"
)
STAT_DESCRIPTION_FILES = (
ACTIVE_SKILL_GEM_STAT_DESCRIPTIONS,
ADVANCED_MOD_STAT_DESCRIPTIONS,
ATLAS_STAT_DESCRIPTIONS,
CHARACTER_PANEL_GAMEPAD_STAT_DESCRIPTIONS,
CHARACTER_PANEL_STAT_DESCRIPTIONS,
CHEST_STAT_DESCRIPTIONS,
ENDGAME_MAP_STAT_DESCRIPTIONS,
EXPEDITION_RELIC_STAT_DESCRIPTIONS,
GEM_STAT_DESCRIPTIONS,
HEIST_EQUIPMENT_STAT_DESCRIPTIONS,
LEAGUESTONE_STAT_DESCRIPTIONS,
MAP_STAT_DESCRIPTIONS,
META_GEM_STAT_DESCRIPTIONS,
MONSTER_STAT_DESCRIPTIONS,
PASSIVE_SKILL_AURA_STAT_DESCRIPTIONS,
PASSIVE_SKILL_STAT_DESCRIPTIONS,
PRIMORDIAL_ALTAR_STAT_DESCRIPTIONS,
SANCTUM_RELIC_STAT_DESCRIPTIONS,
SENTINEL_STAT_DESCRIPTIONS,
SKILL_STAT_DESCRIPTIONS,
STAT_DESCRIPTIONS,
TABLET_STAT_DESCRIPTIONS,
UTILITY_FLASK_BUFF_STAT_DESCRIPTIONS,
)
ITEM_IMAGE_CACHE = "itemImageCache.json"
OLD_ITEM_IMAGE_CACHE = "itemImageCache.old.json"

View File

@@ -0,0 +1,154 @@
from constants.lang import KOREAN, LANG, RUSSIAN
REDUCED_ATTRIBUTE_REQUIREMENTS = 3639275092
SANCTUM_REDUCED_GOLD_MERCHANT_COST = 3096446459
REDUCED_CHARGES_PER_USE = 388617051
FLASK_CHARGES_USED = 644456512
CHARM_CHARGES_USED = 1570770415
SLOWING_POTENCY_ON_YOU = 924253255
BLEED_WHEN_YOU_ARE_HIT = 3423694372
BLEED_WHEN_YOU_ARE_HIT_BY_ATTACK = 2155467472
DAMAGE_TAKEN_WHEN_NOT_HIT_RECENTLY = 67637087
MANA_COST_OF_SKILLS = 474294393
IGNITE_EFFECT_ON_YOU = 1269971728
RITUAL_TABLET_REROLL_COST = 2282052746
RECOVERY_APPLIED_INSTANTLY = 2503377690
INSTANT_RECOVERY = 1526933524
FEWER_ENEMIES_SURROUNDED = 2267564181
SKILL_COOLDOWN_SECONDS = 396200591
MOVEMENT_SPEED_PENALTY = 2590797182
TAKE_PERCENT_BLOCKED_DAMAGE = 2905515354
INCREASED_DAMAGE_TAKEN = 3691641145
BLEED_DURATION_ON_YOU = 1692879867
CURSE_DURATION_ON_YOU = 2920970371
GEAR_RARITY = 3917489142
TABLET_OR_MAP_RARITY = 2306002879
WEAPON_LIFE_LEECH = 55876295
GEAR_LIFE_LEECH = 2557965901
WEAPON_MANA_LEECH = 669069897
GEAR_MANA_LEECH = 707457662
# value stats
JEWEL_RADIUS_CHANGE = 3891355829
JEWEL_RING_RADIUS = 3642528642
ALLOCATES_NOTABLE = 2954116742
ULTIMATUM_WAGER_TYPE = 3076483222
UNIQUE_JEWEL_PLUS_LEVEL = 448592698
DISCONNECTED_PASSIVES_AROUND_KEYSTONE = 2422708892
TIME_LOST_HISTORIC_JEWEL = 3418580811
# =============================================================================
#
#
#
# =============================================================================
"""For stats where we want lower/negative values"""
BETTER_LOOKUP: dict[int, int] = {
REDUCED_ATTRIBUTE_REQUIREMENTS: -1,
SANCTUM_REDUCED_GOLD_MERCHANT_COST: -1,
REDUCED_CHARGES_PER_USE: -1,
SLOWING_POTENCY_ON_YOU: -1,
BLEED_WHEN_YOU_ARE_HIT: -1,
BLEED_WHEN_YOU_ARE_HIT_BY_ATTACK: -1,
DAMAGE_TAKEN_WHEN_NOT_HIT_RECENTLY: -1,
FLASK_CHARGES_USED: -1,
CHARM_CHARGES_USED: -1,
IGNITE_EFFECT_ON_YOU: -1,
JEWEL_RING_RADIUS: 0,
ALLOCATES_NOTABLE: 0,
TIME_LOST_HISTORIC_JEWEL: 0,
RITUAL_TABLET_REROLL_COST: -1,
FEWER_ENEMIES_SURROUNDED: -1,
SKILL_COOLDOWN_SECONDS: -1,
MOVEMENT_SPEED_PENALTY: -1,
TAKE_PERCENT_BLOCKED_DAMAGE: -1,
INCREASED_DAMAGE_TAKEN: -1,
BLEED_DURATION_ON_YOU: -1,
CURSE_DURATION_ON_YOU: -1,
}
"""
For stats where we want lower/negative values
BUT the trade site shows the 'better' version of the string
ex:
trade site shows , positive negative better on trade site
#% increased Attribute Requirements, negative better -> so it is in BETTER_LOOKUP
#% increased Charges per Use , negative better -> so it is in BETTER_LOOKUP
#% reduced Charm Charges Used , positive better -> so it is in FLIPPED_NEGATE
Trade site shows the negated version, and wants positive numbers
"""
TRADE_INVERTED: set[int] = {
FLASK_CHARGES_USED,
CHARM_CHARGES_USED,
# MANA_COST_OF_SKILLS, # Already flipped in descriptions
IGNITE_EFFECT_ON_YOU,
}
VALUE_TO_DESCRIPTION_ID: dict[int, str] = {
JEWEL_RADIUS_CHANGE: "local_jewel_display_radius_change",
JEWEL_RING_RADIUS: "local_jewel_variable_ring_radius_value",
ALLOCATES_NOTABLE: "mod_granted_passive_hash",
ULTIMATUM_WAGER_TYPE: "ultimatum_wager_type_hash",
UNIQUE_JEWEL_PLUS_LEVEL: "unique_jewel_specific_skill_level_+_level",
DISCONNECTED_PASSIVES_AROUND_KEYSTONE: "local_unique_jewel_disconnected_passives_can_be_allocated_around_keystone_hash",
TIME_LOST_HISTORIC_JEWEL: "local_unique_jewel_alternate_tree_seed",
}
VALUE_TO_REF: dict[int, str] = {
JEWEL_RADIUS_CHANGE: "Upgrades Radius to #",
JEWEL_RING_RADIUS: "Only affects Passives in # Ring",
ALLOCATES_NOTABLE: "Allocates #",
ULTIMATUM_WAGER_TYPE: "Sacrifice up to # to receive double on Trial completion",
UNIQUE_JEWEL_PLUS_LEVEL: "# to Level of all # Skills",
DISCONNECTED_PASSIVES_AROUND_KEYSTONE: "Passives in Radius of # can be Allocated\nwithout being connected to your tree",
TIME_LOST_HISTORIC_JEWEL: "Remembrancing # songworthy deeds by the line of #\nPassives in radius are Conquered by the Kalguur",
}
SUPPORTED_VALUES: set[int] = {
JEWEL_RADIUS_CHANGE,
JEWEL_RING_RADIUS,
ALLOCATES_NOTABLE,
TIME_LOST_HISTORIC_JEWEL,
DISCONNECTED_PASSIVES_AROUND_KEYSTONE,
UNIQUE_JEWEL_PLUS_LEVEL,
}
UNIQUE_ITEMS_FIXED_STATS: dict[str, list[str]] = {
"Darkness Enthroned": ["#% increased effect of Socketed Items", "Has # Charm Slot"],
"Cursecarver": [
"#% increased Spell Damage",
"#% increased Cast Speed",
"#% increased Mana Regeneration Rate",
"Gain # Life per Enemy Killed",
],
"The Unborn Lich": ["#% increased Desecrated Modifier magnitudes"],
"Morior Invictus": ["#% increased Armour, Evasion and Energy Shield"],
"Rite of Passage": ["Used when you Kill a Rare or Unique Enemy"],
"Grip of Kulemak": [
"Inflict Abyssal Wasting on Hit",
"#% increased Presence Area of Effect",
"#% increased Light Radius",
],
"Heroic Tragedy": ["Historic"],
"Undying Hate": ["Historic"],
"Megalomaniac": [],
"From Nothing": [],
"Prism of Belief": [],
"Heart of the Well": [],
"Against the Darkness": [],
}
SAME_TRANSLATIONS_DIFFERENT_STATS: dict[LANG, dict[str, list[str]]] = {
RUSSIAN: {
f"explicit.stat_{GEAR_RARITY}": [f"explicit.stat_{TABLET_OR_MAP_RARITY}"],
f"explicit.stat_{TABLET_OR_MAP_RARITY}": [f"explicit.stat_{GEAR_RARITY}"],
},
KOREAN: {
f"explicit.stat_{WEAPON_LIFE_LEECH}": [f"explicit.stat_{GEAR_LIFE_LEECH}"],
f"explicit.stat_{GEAR_LIFE_LEECH}": [f"explicit.stat_{WEAPON_LIFE_LEECH}"],
f"explicit.stat_{WEAPON_MANA_LEECH}": [f"explicit.stat_{GEAR_MANA_LEECH}"],
f"explicit.stat_{GEAR_MANA_LEECH}": [f"explicit.stat_{WEAPON_MANA_LEECH}"],
},
}

View File

@@ -0,0 +1,49 @@
from typing import Literal
LANG = Literal["cmn-Hant", "en", "de", "ja", "ko", "ru", "es", "pt"]
ALL_LANG = Literal[
"cmn-Hant", "en", "de", "es", "ja", "ko", "ru", "fr", "pt", "th", "zh-Hans"
]
CHINESE: LANG = "cmn-Hant"
ENGLISH: LANG = "en"
GERMAN: LANG = "de"
SPANISH: LANG = "es"
JAPANESE: LANG = "ja"
KOREAN: LANG = "ko"
RUSSIAN: LANG = "ru"
PORTUGUESE: LANG = "pt"
# Extra that are in game, but not used by EE2
FRENCH: ALL_LANG = "fr"
THAI: ALL_LANG = "th"
SIMPLIFIED_CHINESE: ALL_LANG = "zh-Hans"
LANGUAGES: set[LANG] = {
ENGLISH,
CHINESE,
GERMAN,
SPANISH,
JAPANESE,
KOREAN,
RUSSIAN,
PORTUGUESE,
}
LANGUAGES_NAMES: dict[ALL_LANG, str] = {
ENGLISH: "English",
CHINESE: "Traditional Chinese",
GERMAN: "German",
SPANISH: "Spanish",
JAPANESE: "Japanese",
KOREAN: "Korean",
RUSSIAN: "Russian",
FRENCH: "French",
PORTUGUESE: "Portuguese",
THAI: "Thai",
SIMPLIFIED_CHINESE: "Simplified Chinese",
}
LANGUAGES_NAMES_TO_CODES: dict[str, ALL_LANG] = {
v: k for k, v in LANGUAGES_NAMES.items()
}

View File

@@ -0,0 +1,37 @@
from typing import Literal
MOD_TYPE = Literal[
"explicit",
"implicit",
"fractured",
"enchant",
"rune",
"desecrated",
"sanctum",
"skill",
"pseudo",
]
EXPLICIT: MOD_TYPE = "explicit"
IMPLICIT: MOD_TYPE = "implicit"
FRACTURED: MOD_TYPE = "fractured"
ENCHANT: MOD_TYPE = "enchant"
RUNE: MOD_TYPE = "rune"
DESECRATED: MOD_TYPE = "desecrated"
SANCTUM: MOD_TYPE = "sanctum"
SKILL: MOD_TYPE = "skill"
PSEUDO: MOD_TYPE = "pseudo"
MOD_TYPES: list[MOD_TYPE] = [
EXPLICIT,
IMPLICIT,
FRACTURED,
ENCHANT,
RUNE,
DESECRATED,
SANCTUM,
SKILL,
PSEUDO,
]
MOD_TYPES_SET: set[MOD_TYPE] = set(MOD_TYPES)

View File

@@ -0,0 +1,12 @@
LOCAL_SUFFIX = " (Local)"
GENDER_CLIENT_STRINGS_ORDER = [
"NS",
"MS",
"FS",
"NP",
"O",
"ANY",
"M",
"F",
]

View File

@@ -0,0 +1,28 @@
from constants.lang import (
CHINESE,
ENGLISH,
GERMAN,
JAPANESE,
KOREAN,
PORTUGUESE,
RUSSIAN,
SPANISH,
)
LANG_URLS = {
ENGLISH: "https://www.pathofexile.com",
RUSSIAN: "https://ru.pathofexile.com",
KOREAN: "https://poe.game.daum.net",
CHINESE: "https://pathofexile.tw",
JAPANESE: "https://jp.pathofexile.com",
GERMAN: "https://de.pathofexile.com",
SPANISH: "https://es.pathofexile.com",
PORTUGUESE: "https://br.pathofexile.com",
}
TRADE_QUERY_URLS = [
"/api/trade2/data/filters",
"/api/trade2/data/stats",
"/api/trade2/data/items",
"/api/trade2/data/static",
]

View File

@@ -0,0 +1,42 @@
import re
from abc import ABC, abstractmethod
from typing import Callable
from constants.lang import LANG
from constants.other import GENDER_CLIENT_STRINGS_ORDER
class BaseClientString(ABC):
gender_pattern: re.Pattern[str] = re.compile(
r"<([efils]*):?(?P<gender>.{1,2})?>\{{1,2}(?P<key>[^{}]*)\}{1,2}"
)
sub_gender_pattern: re.Pattern[str] = re.compile(r"<.*\{{1,2}[^{}0]+\}{1,2}")
@property
@abstractmethod
def my_key(self) -> str:
raise NotImplementedError()
@abstractmethod
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
raise NotImplementedError()
@abstractmethod
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
raise NotImplementedError()
def replace_gender_if_block(self, line: str) -> str:
if self.gender_pattern.search(line) is None:
return line
genders: dict[str, str] = {}
for m in self.gender_pattern.finditer(line):
if m.group("gender") is None:
genders["ANY"] = m.group("key")
continue
genders[m.group("gender")] = m.group("key")
for gender in GENDER_CLIENT_STRINGS_ORDER:
if gender in genders:
return self.sub_gender_pattern.sub(genders[gender], line)
return line

View File

@@ -0,0 +1,19 @@
from abc import ABC, abstractmethod
from typing import Callable
from constants.lang import LANG
class RegexClientString(ABC):
@property
@abstractmethod
def my_key(self) -> str:
raise NotImplementedError()
@abstractmethod
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
raise NotImplementedError()
@abstractmethod
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
raise NotImplementedError()

284
dataParser/src/cs.ipynb Normal file
View File

@@ -0,0 +1,284 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "460ea25d",
"metadata": {},
"outputs": [],
"source": [
"from services.client_strings_builder import ClientStringsBuilder\n",
"from constants.client_string_data import CLIENT_STRING_KEY_VALUES, CLIENT_STRING_ARRAYS, CLIENT_STRING_REGEX\n",
"from models.client_string import ClientString\n",
"\n",
"from constants.lang import ENGLISH, RUSSIAN, GERMAN, JAPANESE"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "c1c9cffe",
"metadata": {},
"outputs": [],
"source": [
"\n",
"builder = ClientStringsBuilder(ENGLISH)\n",
"# builder = ClientStringsBuilder(RUSSIAN)\n",
"# builder = ClientStringsBuilder(GERMAN)\n",
"# builder = ClientStringsBuilder(JAPANESE)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7a7c452d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"// @ts-check\n",
"/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */\n",
"export default {\n",
"\n",
"\n",
"\n",
"}\n"
]
}
],
"source": [
"out = builder.build([],[], [])\n",
"print(out)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "8691a050",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"// @ts-check\n",
"/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */\n",
"export default {\n",
" RARITY_NORMAL: 'Normal',\n",
"\n",
"\n",
"}\n"
]
}
],
"source": [
"test = ClientString(\"RARITY_NORMAL\", [\"ItemDisplayStringNormal\"])\n",
"out = builder.build(base_strings=[test], array_strings=[], regex_strings=[])\n",
"print(out)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c4f0693b",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"// @ts-check\n",
"/** @type{import('../../../src/assets/data/interfaces').TranslationDict} */\n",
"export default {\n",
" RARITY_NORMAL: 'Normal',\n",
" RARITY_MAGIC: 'Magic',\n",
" RARITY_RARE: 'Rare',\n",
" RARITY_UNIQUE: 'Unique',\n",
" RARITY_GEM: 'Gem',\n",
" RARITY_CURRENCY: 'Currency',\n",
" RARITY_DIVCARD: 'Divination Card',\n",
" RARITY_QUEST: 'Quest',\n",
" MAP_TIER: 'Map Tier: ',\n",
" RARITY: 'Rarity: ',\n",
" ITEM_CLASS: 'Item Class: ',\n",
" ITEM_LEVEL: 'Item Level: ',\n",
" CORPSE_LEVEL: 'Corpse Level: ',\n",
" TALISMAN_TIER: 'Talisman Tier: ',\n",
" GEM_LEVEL: 'Level: ',\n",
" STACK_SIZE: 'Stack Size: ',\n",
" SOCKETS: 'Sockets: ',\n",
" QUALITY: 'Quality: ',\n",
" PHYSICAL_DAMAGE: 'Physical Damage: ',\n",
" ELEMENTAL_DAMAGE: 'Elemental Damage: ',\n",
" LIGHTNING_DAMAGE: 'Lightning Damage: ',\n",
" COLD_DAMAGE: 'Cold Damage: ',\n",
" FIRE_DAMAGE: 'Fire Damage: ',\n",
" CRIT_CHANCE: 'Critical Hit Chance: ',\n",
" ATTACK_SPEED: 'Attacks per Second: ',\n",
" ARMOUR: 'Armour: ',\n",
" EVASION: 'Evasion Rating: ',\n",
" ENERGY_SHIELD: 'Energy Shield: ',\n",
" BLOCK_CHANCE: 'Block chance: ',\n",
" CORRUPTED: 'Corrupted',\n",
" UNIDENTIFIED: 'Unidentified',\n",
" INFLUENCE_SHAPER: 'Shaper Item',\n",
" INFLUENCE_ELDER: 'Elder Item',\n",
" INFLUENCE_CRUSADER: 'Crusader Item',\n",
" INFLUENCE_HUNTER: 'Hunter Item',\n",
" INFLUENCE_REDEEMER: 'Redeemer Item',\n",
" INFLUENCE_WARLORD: 'Warlord Item',\n",
" SECTION_SYNTHESISED: 'Synthesised Item',\n",
" VEILED_PREFIX: 'Desecrated Prefix',\n",
" VEILED_SUFFIX: 'Desecrated Suffix',\n",
" METAMORPH_HELP: 'Combine this with four other different samples in Tane\\'s Laboratory.',\n",
" BEAST_HELP: 'Right-click to add this to your bestiary.',\n",
" VOIDSTONE_HELP: 'Socket this into your Atlas to increase the Tier of all Maps.',\n",
" CANNOT_USE_ITEM: 'You cannot use this item. Its stats will be ignored',\n",
" AREA_LEVEL: 'Area Level: ',\n",
" HEIST_WINGS_REVEALED: 'Wings Revealed: ',\n",
" HEIST_TARGET: 'Heist Target: ',\n",
" HEIST_BLUEPRINT_ENCHANTS: 'Enchanted Armaments',\n",
" HEIST_BLUEPRINT_TRINKETS: 'Thieves\\' Trinkets or Currency',\n",
" HEIST_BLUEPRINT_GEMS: 'Unusual Gems',\n",
" HEIST_BLUEPRINT_REPLICAS: 'Replicas or Experimented Items',\n",
" MIRRORED: 'Mirrored',\n",
" PREFIX_MODIFIER: 'Prefix Modifier',\n",
" SUFFIX_MODIFIER: 'Suffix Modifier',\n",
" CRAFTED_PREFIX: 'Master Crafted Prefix Modifier',\n",
" CRAFTED_SUFFIX: 'Master Crafted Suffix Modifier',\n",
" UNSCALABLE_VALUE: ' — Unscalable Value',\n",
" CORRUPTED_IMPLICIT: 'Corruption Implicit Modifier',\n",
" ELDRITCH_MOD_R1: 'Lesser',\n",
" ELDRITCH_MOD_R2: 'Greater',\n",
" ELDRITCH_MOD_R3: 'Grand',\n",
" ELDRITCH_MOD_R4: 'Exceptional',\n",
" ELDRITCH_MOD_R5: 'Exquisite',\n",
" ELDRITCH_MOD_R6: 'Perfect',\n",
" SENTINEL_CHARGE: 'Charge: ',\n",
" FOIL_UNIQUE: 'Foil Unique (',\n",
" UNMODIFIABLE: 'Unmodifiable',\n",
" REQUIREMENTS: 'Requirements',\n",
" CHARM_SLOTS: 'Charm Slots: ',\n",
" BASE_SPIRIT: 'Spirit: ',\n",
" QUIVER_HELP_TEXT: 'Can only be equipped if you are wielding a Bow.',\n",
" FLASK_HELP_TEXT: 'Right click to drink. Can only hold charges while in belt. Refill at Wells or by killing monsters.',\n",
" CHARM_HELP_TEXT: 'Used automatically when condition is met. Can only hold charges while in belt. Refill at Wells or by killing monsters.',\n",
" PRICE_NOTE: 'Note: ',\n",
" WAYSTONE_TIER: 'Waystone Tier: ',\n",
" WAYSTONE_HELP: 'Can be used in a Map Device, allowing you to enter a Map. Waystones can only be used once.',\n",
" JEWEL_HELP: 'Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.',\n",
" SANCTUM_HELP: 'Place this item on the Relic Altar at the start of the Trial of the Sekhemas',\n",
" TIMELESS_RADIUS: 'Radius: ',\n",
" PRECURSOR_TABLET_HELP: 'Can be used in a personal Map Device to add modifiers to a Map.',\n",
" LOGBOOK_HELP: 'Take this item to Dannig in your Hideout to open portals to an expedition.',\n",
" REQUIRES: 'Requires: ',\n",
" TIMELESS_SMALL_PASSIVES: 'Small Passive Skills in Radius also grant {0}',\n",
" TIMELESS_NOTABLE_PASSIVES: 'Notable Passive Skills in Radius also grant {0}',\n",
" GRANTS_SKILL: 'Grants Skill: ',\n",
" RELOAD_SPEED: 'Reload Time: ',\n",
" FRACTURED_ITEM: 'Fractured Item',\n",
" SANCTIFIED: 'Sanctified',\n",
" HYPHEN: '-',\n",
" WAYSTONE_REVIVES: 'Revives Available: ',\n",
" WAYSTONE_PACK_SIZE: 'Monster Pack Size: ',\n",
" WAYSTONE_MAGIC_MONSTERS: 'Magic Monsters: ',\n",
" WAYSTONE_RARE_MONSTERS: 'Rare Monsters: ',\n",
" WAYSTONE_DROP_CHANCE: 'Waystone Drop Chance: ',\n",
" WAYSTONE_RARITY: 'Item Rarity: ',\n",
" // [Array]\n",
" SHAPER_MODS: ['of Shaping', 'The Shaper\\'s'],\n",
" // [Array]\n",
" ELDER_MODS: ['of the Elder', 'The Elder\\'s'],\n",
" // [Array]\n",
" CRUSADER_MODS: ['Crusader\\'s', 'of the Crusade'],\n",
" // [Array]\n",
" HUNTER_MODS: ['Hunter\\'s', 'of the Hunt'],\n",
" // [Array]\n",
" REDEEMER_MODS: ['Redeemer\\'s', 'of Redemption'],\n",
" // [Array]\n",
" WARLORD_MODS: ['Warlord\\'s', 'of the Conquest'],\n",
" // [Array]\n",
" DELVE_MODS: ['Subterranean', 'of the Underground'],\n",
" // [Array]\n",
" VEILED_MODS: ['Chosen', 'of the Order'],\n",
" // [Array]\n",
" INCURSION_MODS: ['Guatelitzi\\'s', 'Xopec\\'s', 'Topotante\\'s', 'Tacati\\'s', 'Matatl\\'s', 'of Matatl', 'Citaqualotl\\'s', 'of Citaqualotl', 'of Tacati', 'of Guatelitzi', 'of Puhuarte'],\n",
" ITEM_SUPERIOR: /^Superior (.*)$/,\n",
" ITEM_EXCEPTIONAL: /^Exceptional (.*)$/,\n",
" MAP_BLIGHTED: /^Blighted (.*)$/,\n",
" MAP_BLIGHT_RAVAGED: /^Blight-ravaged (.*)$/,\n",
" ITEM_SYNTHESISED: /^Synthesised (.*)$/,\n",
" FLASK_CHARGES: /^Currently has \\d+ Charges$/,\n",
" // [Manual]\n",
" METAMORPH_BRAIN: /^.* Brain$/,\n",
" // [Manual]\n",
" METAMORPH_EYE: /^.* Eye$/,\n",
" // [Manual]\n",
" METAMORPH_LUNG: /^.* Lung$/,\n",
" // [Manual]\n",
" METAMORPH_HEART: /^.* Heart$/,\n",
" // [Manual]\n",
" METAMORPH_LIVER: /^.* Liver$/,\n",
" QUALITY_ANOMALOUS: /^Anomalous (.*)$/,\n",
" QUALITY_DIVERGENT: /^Divergent (.*)$/,\n",
" QUALITY_PHANTASMAL: /^Phantasmal (.*)$/,\n",
" MODIFIER_LINE: /^(?<type>[^\"]+)(?:\\s+\"(?<name>[^\"]*)\")?(?:\\s+\\(Tier: (?<tier>\\d+)\\))?(?:\\s+\\(Rank: (?<rank>\\d+)\\))?$/,\n",
" MODIFIER_INCREASED: /^ — (.*)% Increased$/,\n",
" EATER_IMPLICIT: /^Eater of Worlds Implicit Modifier \\((?<rank>.+)\\)$/,\n",
" EXARCH_IMPLICIT: /^Searing Exarch Implicit Modifier \\((?<rank>.+)\\)$/,\n",
" // [Manual]\n",
" CHAT_SYSTEM: /^: (?<body>.+)$/,\n",
" // [Manual]\n",
" CHAT_TRADE: /^\\$(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,\n",
" // [Manual]\n",
" CHAT_GLOBAL: /^&#(?<guild_tag>.+?); (?<char_name>.+?): (?<body>.+)$/,\n",
" // [Manual]\n",
" CHAT_PARTY: /^%(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,\n",
" // [Manual]\n",
" CHAT_GUILD: /^&(?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,\n",
" CHAT_WHISPER_TO: /^@To (?<char_name>.+?): (?<body>.+)$/,\n",
" CHAT_WHISPER_FROM: /^@From (?:<(?<guild_tag>.+?)> )?(?<char_name>.+?): (?<body>.+)$/,\n",
" // [Manual]\n",
" CHAT_WEBTRADE_GEM: /^level (?<gem_lvl>\\d+) (?<gem_qual>\\d+)% (?<gem_name>.+)$/,\n",
"}\n"
]
}
],
"source": [
"actual = builder.build(CLIENT_STRING_KEY_VALUES, CLIENT_STRING_ARRAYS, CLIENT_STRING_REGEX)\n",
"print(actual)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff844d79",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

125
dataParser/src/main.py Normal file
View File

@@ -0,0 +1,125 @@
import argparse
import json
import logging
import shutil
from tqdm import tqdm
from constants.client_string_data import (
CLIENT_STRING_ARRAYS,
CLIENT_STRING_KEY_VALUES,
CLIENT_STRING_REGEX,
)
from constants.lang import ENGLISH, LANG, LANGUAGES
from providers.game_api import GameDataProvider
from providers.trade_api import TradeApiProvider
from services.client_strings_builder import ClientStringsBuilder
from services.image_provider import MODE
from services.nd_builder_service import NdBuilderService
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def main(**kwargs: dict[str, bool | str]):
pull: bool = kwargs.get("pull", False) # pyright: ignore[reportAssignmentType]
image_mode: MODE = kwargs.get("image_mode", "new") # pyright: ignore[reportAssignmentType]
push: bool = kwargs.get("push", False) # pyright: ignore[reportAssignmentType]
main_repo_path: str = kwargs.get("main_repo_path", "../exiled-exchange-2") # pyright: ignore[reportAssignmentType]
if pull:
GameDataProvider().run_export()
for lang in tqdm(LANGUAGES):
TradeApiProvider(lang).pull()
run_on: list[LANG] = [ENGLISH, *[lang for lang in LANGUAGES if lang != ENGLISH]]
for index, lang in enumerate(run_on):
logger.info(
"================================================================================"
)
logger.info(f"Building {lang}: {index + 1}/{len(LANGUAGES)}")
logger.info(
"================================================================================"
)
nd = NdBuilderService(lang, image_mode if lang is ENGLISH else "new")
stats = nd.build_stats_ndjson()
stats_o = stats.where(stats.notna(), None)
with open(f"output/{lang}/stats.ndjson", "w", encoding="utf-8") as f:
for _, row in stats_o.iterrows():
obj = {k: v for k, v in row.items() if v is not None}
f.write(f"{json.dumps(obj, ensure_ascii=False)}\n")
items = nd.build_items_ndjson()
items_o = items.where(items.notna(), None)
with open(f"output/{lang}/items.ndjson", "w", encoding="utf-8") as f:
for _, row in items_o.iterrows():
obj = {k: v for k, v in row.items() if v is not None}
try:
f.write(f"{json.dumps(obj, ensure_ascii=False)}\n")
except Exception as e:
print(e)
print(obj)
raise
client_strings_builder = ClientStringsBuilder(lang)
client_strings_js = client_strings_builder.build(
base_strings=CLIENT_STRING_KEY_VALUES,
array_strings=CLIENT_STRING_ARRAYS,
regex_strings=CLIENT_STRING_REGEX,
)
with open(f"output/{lang}/client_strings.js", "w", encoding="utf-8") as f:
try:
f.write(client_strings_js)
except Exception as e:
print(e)
print(client_strings_js)
raise
# Copy files to EE2 repo
if push:
logger.info("Copying files to EE2 repo")
_ = shutil.copy(
f"output/{lang}/stats.ndjson",
f"{main_repo_path}/renderer/public/data/{lang}/stats.ndjson",
)
_ = shutil.copy(
f"output/{lang}/items.ndjson",
f"{main_repo_path}/renderer/public/data/{lang}/items.ndjson",
)
_ = shutil.copy(
f"output/{lang}/client_strings.js",
f"{main_repo_path}/renderer/public/data/{lang}/client_strings.js",
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
_ = parser.add_argument("--pull", action="store_true", help="Pull trade data")
_ = parser.add_argument(
"--log", default="info", help="Log level (debug, info, warn, error)"
)
_ = parser.add_argument(
"--image-mode",
default="new",
choices=["noLookup", "missing", "new", "all"],
help="Image mode (noLookup, missing, new, all)",
)
_ = parser.add_argument(
"--push",
action="store_true",
help="Push to EE2 repo",
)
_ = parser.add_argument(
"--main-repo-path",
default="../exiled-exchange-2",
help="Path to main repo",
)
args = parser.parse_args()
logging.getLogger().setLevel(args.log.upper()) # pyright:ignore [reportAny]
main(**vars(args)) # pyright:ignore [reportAny]

View File

@@ -0,0 +1,34 @@
import logging
from collections.abc import Sequence
from typing import Callable, override
from constants.lang import LANG
from contracts.models.base_client_string import BaseClientString
logger = logging.getLogger(__name__)
class ArrayClientString:
def __init__(self, values: Sequence[BaseClientString]):
if len(values) == 0:
raise ValueError("ArrayClientString must have at least one value")
if not all([v.my_key == values[0].my_key for v in values]):
raise ValueError("All values must have the same key")
self.my_key: str = values[0].my_key
self.values: Sequence[BaseClientString] = values
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
logger.debug(self)
value = self.value(poe_key_lookup, lang)
return f" // [Array]\n {self.my_key}: {value},"
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
return (
"["
+ ", ".join(f"'{v.value(poe_key_lookup, lang)}'" for v in self.values)
+ "]"
)
@override
def __repr__(self) -> str:
return f"ArrayClientString({self.my_key}, {'\n\t\t'.join(repr(v) for v in self.values)}\n\t)"

View File

@@ -0,0 +1,63 @@
import logging
import re
from re import Pattern
from typing import Callable, override
from constants.lang import LANG
from contracts.models.regex_client_string import RegexClientString
from models.client_string import ClientString
logger = logging.getLogger(__name__)
class CapturePlaceholderClientString(ClientString, RegexClientString):
format_pattern: Pattern[str] = re.compile(r"\{\d*\}")
capture_str: str = "(.*)"
def __init__(
self,
my_key: str,
poe_keys: list[str],
format: str | None = None,
substring: list[
tuple[int | Callable[[str], int], int | Callable[[str], int] | None] | None
]
| None = None,
keep_tooltip: bool = False,
keep_format_option: bool = False,
trim: bool = False,
capture_str: str = "(.*)",
):
super().__init__(
my_key,
poe_keys,
format,
substring,
keep_tooltip,
keep_format_option,
trim,
)
self.capture_str = capture_str
@override
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
logger.debug(self)
out_out = self.value(poe_key_lookup, lang)
out_string = "\n".join(
[
# f" // [{self.apply(self.poe_keys)}]",
f" {self.my_key}: /^{out_out}$/,",
]
)
return out_string
@override
def replace_format_things(self, line: str) -> str:
line = line.replace("(", "\\(").replace(")", "\\)")
line = re.sub(self.format_pattern, self.capture_str, line)
line = super().replace_format_things(line)
return line
@override
def __repr__(self) -> str:
return f"CapturePlaceholderClientString(my_key={self.my_key}, poe_keys={self.poe_keys}, format={self.format},\n\t\t substring={self.substring}, keep_tooltip={self.keep_tooltip}, keep_format_option={self.keep_format_option},\n\t\t trim={self.trim}, capture_str={self.capture_str})"

View File

@@ -0,0 +1,142 @@
import logging
import re
from re import Pattern
from typing import Callable, override
from constants.lang import LANG
from contracts.models.base_client_string import BaseClientString
MAX_ITER = 25
logger = logging.getLogger(__name__)
class ClientString(BaseClientString):
pattern_full: Pattern[str] = re.compile(r"\[([^\|\]]*?)\|([^\]]+)\]")
_my_key: str
def __init__(
self,
my_key: str,
poe_keys: list[str],
format: str | None = None,
substring: list[
tuple[int | Callable[[str], int], int | Callable[[str], int] | None] | None
]
| None = None,
keep_tooltip: bool = False,
keep_format_option: bool = False,
trim: bool = False,
):
self._my_key = my_key
self.poe_keys: list[str] = poe_keys
self.format: str | None = format
self.substring: (
list[
tuple[int | Callable[[str], int], int | Callable[[str], int] | None]
| None
]
| None
) = substring
self.keep_tooltip: bool = keep_tooltip
self.keep_format_option: bool = keep_format_option
self.trim: bool = trim
def apply(self, values: list[str]) -> str:
if self.format is None:
return "".join(values)
return self.format.format(*values)
@property
@override
def my_key(self) -> str:
return self._my_key
@override
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
logger.debug(self)
out_out = self.value(poe_key_lookup, lang)
out_string = "\n".join(
[
# f" // [{self.apply(self.poe_keys)}]",
f" {self.my_key}: '{out_out}',",
]
)
return out_string
@override
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
out_values = [poe_key_lookup(x) for x in self.poe_keys]
out_values = [self.replace_gender_if_block(x) for x in out_values]
if not self.keep_tooltip:
out_values = [self.replace_tooltip_tags(x) for x in out_values]
if not self.keep_format_option:
out_values = [self.replace_format_things(x) for x in out_values]
if self.substring is not None:
out_values = [
self.apply_substring(x, y) for x, y in zip(out_values, self.substring)
]
out_out = self.apply(out_values)
if self.trim:
out_out = out_out.strip()
out_out = out_out.replace("'", "\\'")
return out_out
def apply_substring(
self,
value: str,
substring: tuple[int | Callable[[str], int], int | Callable[[str], int] | None]
| None,
) -> str:
if substring is None:
return value
if isinstance(substring[0], Callable):
first = substring[0](value)
else:
first = substring[0]
if substring[1] is None:
return value[first:]
if isinstance(substring[1], Callable):
return value[first : substring[1](value)]
return value[first : substring[1]]
def replace_tooltip_tags(self, line: str) -> str:
for _ in range(MAX_ITER):
if not re.search(self.pattern_full, line):
break
line = re.sub(
self.pattern_full,
r"\2" if self.pattern_full.groups == 2 else r"\1",
line,
)
else:
raise RuntimeError(
f"Exceeded max iterations for pattern: {self.pattern_full.pattern}"
)
return line.replace("[", "").replace("]", "")
def replace_format_things(self, line: str) -> str:
for _ in range(MAX_ITER):
start = line.find("{")
end = line.find("}")
if start == -1 or end == -1:
break
line = line[:start] + line[end + 1 :]
else:
raise RuntimeError(
f"Exceeded max iterations for pattern: {self.pattern_full.pattern}"
)
return line
@override
def __repr__(self) -> str:
return f"ClientString(my_key={self.my_key}, poe_keys={self.poe_keys}, format={self.format},\n\t\t substring={self.substring}, keep_tooltip={self.keep_tooltip}, keep_format_option={self.keep_format_option},\n\t\t trim={self.trim})"

View File

@@ -0,0 +1,42 @@
import logging
from typing import Callable, override
from constants.lang import LANG
from contracts.models.base_client_string import BaseClientString
logger = logging.getLogger(__name__)
class ConstClientString(BaseClientString):
need_lookup: bool
_my_key: str
def __init__(self, key: str, output: str | dict[LANG, str]):
self._my_key = key
if isinstance(output, str):
self.const: str = output
self.need_lookup = False
else:
self.lookup: dict[LANG, str] = output
self.need_lookup = True
@property
@override
def my_key(self) -> str:
return self._my_key
@override
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
logger.debug(self)
value = self.value(poe_key_lookup, lang)
return f" // [Manual]\n {self.my_key}: '{value}',"
@override
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
val = self.const if not self.need_lookup else self.lookup[lang]
val = val.replace("'", "\\'")
return val
@override
def __repr__(self) -> str:
return f"ConstClientString(my_key={self.my_key}, const={self.const}, need_lookup={self.need_lookup})"

View File

@@ -0,0 +1,42 @@
import logging
from typing import Callable, override
from constants.lang import LANG
from contracts.models.regex_client_string import RegexClientString
logger = logging.getLogger(__name__)
class ConstRegexClientString(RegexClientString):
need_lookup: bool
_my_key: str
def __init__(self, key: str, output: str | dict[LANG, str]):
self._my_key = key
if isinstance(output, str):
self.const: str = output
self.need_lookup = False
else:
self.lookup: dict[LANG, str] = output
self.need_lookup = True
@property
@override
def my_key(self) -> str:
return self._my_key
@override
def string(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
logger.debug(self)
value = self.value(poe_key_lookup, lang)
return f" // [Manual]\n {self.my_key}: /^{value}$/,"
@override
def value(self, poe_key_lookup: Callable[[str], str], lang: LANG) -> str:
val = self.const if not self.need_lookup else self.lookup[lang]
val = val.replace("'", "\\'")
return val
@override
def __repr__(self) -> str:
return f"ConstRegexClientString(my_key={self.my_key}, const={self.const}, need_lookup={self.need_lookup})"

View File

@@ -0,0 +1,62 @@
# pyright: basic
import copy
from typing import Literal
UNIQUE_FILTER = {"type_filters": {"filters": {"rarity": {"option": "unique"}}}}
NONUNIQUE_FILTER = {"type_filters": {"filters": {"rarity": {"option": "nonunique"}}}}
BASE_PAYLOAD: dict[
str, dict[str, dict[str, str] | list[dict[str, str | list[str]]]] | dict[str, str]
] = {
"query": {
"status": {"option": "any"},
"stats": [{"type": "and", "filters": []}],
},
"sort": {"price": "asc"},
}
class ItemImage:
def __init__(
self,
name: str,
namespace: Literal["UNIQUE", "ITEM", "GEM"],
unique: dict[str, str] | None = None,
):
self.name = name
self.namespace = namespace
if namespace == "UNIQUE":
assert unique is not None
assert unique.get("base") is not None
self.base = unique.get("base")
else:
self.base = name
def __str__(self):
return f"{self.name} ({self.namespace})"
def __repr__(self):
return f"{self.name} ({self.namespace})"
def __eq__(self, other):
if not isinstance(other, ItemImage):
return False
return self.name == other.name and self.namespace == other.namespace
def __hash__(self):
return hash((self.name, self.namespace))
def search_payload(self):
assert self.base is not None
payload = copy.deepcopy(BASE_PAYLOAD)
if self.namespace == "UNIQUE":
payload["query"]["filters"] = UNIQUE_FILTER
payload["query"]["type"] = self.base
payload["query"]["name"] = self.name
else:
payload["query"]["filters"] = NONUNIQUE_FILTER
payload["query"]["type"] = self.name
return payload
def save_name(self) -> str:
return f"{self.namespace}={self.name}"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
import logging
import os
import subprocess
class GameDataProvider:
def __init__(self):
self.vendor_dir: str = os.path.join(
os.path.dirname(__file__), "../../data/vendor"
)
def run_export(self):
try:
_ = subprocess.run(
["pathofexile-dat"], cwd=self.vendor_dir, check=True, shell=True
)
logging.info(f"Exported game data to: {self.vendor_dir}")
except subprocess.CalledProcessError as e:
logging.error(f"Game data export failed: {e}")

View File

@@ -0,0 +1,48 @@
"""
Retrieves all data that is sourced from the trade site
"""
import json
import logging
import os
import cloudscraper
from constants.lang import LANG
from constants.urls import LANG_URLS, TRADE_QUERY_URLS
class TradeApiProvider:
def __init__(self, lang: LANG):
self.lang: LANG = lang
self.session: cloudscraper.CloudScraper = cloudscraper.create_scraper( # pyright:ignore [reportAny]
interpreter="nodejs"
)
self.output_dir: str = os.path.join(
os.path.dirname(__file__), "../../data/json", self.lang
)
def pull(self):
base_url = LANG_URLS[self.lang]
for relative_url in TRADE_QUERY_URLS:
# Construct the full URL
url = f"{base_url}{relative_url}"
# Extract the filename from the relative URL
filename = os.path.basename(relative_url)
logging.info(f"Fetching JSON from: {url} for language: {self.lang}")
# Fetch the JSON data
response = self.session.get(url)
if response.status_code == 200:
output_path = os.path.join(self.output_dir, f"{filename}.json")
with open(output_path, "w", encoding="utf-8") as file:
json.dump(response.json(), file, ensure_ascii=False, indent=4)
logging.info(f"Saved JSON to: {output_path}")
else:
logging.error(
f"Failed to fetch JSON from: {url}, code: {response.status_code}"
)

22297
dataParser/src/scratch.ipynb Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
import logging
from collections.abc import Sequence
from constants.client_string_data import (
CLIENT_STRING_JS_FOOTER,
CLIENT_STRING_JS_HEADER,
)
from constants.filenames import CLIENT_STRINGS
from constants.lang import LANG
from contracts.models.base_client_string import BaseClientString
from contracts.models.regex_client_string import RegexClientString
from models.array_client_string import ArrayClientString
from stores.game_store import GameStore
logger = logging.getLogger(__name__)
class ClientStringsBuilder:
def __init__(self, lang: LANG):
self.lang: LANG = lang
game_store = GameStore(lang)
client_strings_df = game_store.get(CLIENT_STRINGS).set_index("Id") # pyright: ignore[reportUnknownMemberType]
self.client_strings_lookup: dict[str, str] = client_strings_df["Text"].to_dict() # pyright: ignore[reportUnknownMemberType]
def use_game_store(self, poe_key: str) -> str:
logger.debug(f"Using game store for {poe_key}")
return self.client_strings_lookup[poe_key]
def build(
self,
base_strings: Sequence[BaseClientString],
array_strings: list[ArrayClientString],
regex_strings: list[RegexClientString],
) -> str:
logger.info("Building client strings")
base = "\n".join(s.string(self.use_game_store, self.lang) for s in base_strings)
logger.info("Finished base strings")
arrays = "\n".join(
s.string(self.use_game_store, self.lang) for s in array_strings
)
logger.info("Finished array strings")
regexes = "\n".join(
s.string(self.use_game_store, self.lang) for s in regex_strings
)
logger.info("Finished regex strings")
format = [
CLIENT_STRING_JS_HEADER.strip(),
base,
arrays,
regexes,
CLIENT_STRING_JS_FOOTER.strip(),
]
logger.info("Joined strings")
return "\n".join(format)

View File

@@ -0,0 +1,139 @@
import json
import logging
import os
from math import nan
from typing import Literal
import numpy as np
import pandas as pd
from tqdm import tqdm
from constants.filenames import ITEM_IMAGE_CACHE, OLD_ITEM_IMAGE_CACHE
from models.item_image import ItemImage
from services.rate_limiter import RateLimiter
tqdm.pandas()
MODE = Literal["noLookup", "missing", "new", "all"]
logger = logging.getLogger(__name__)
SEARCH_URL = "https://www.pathofexile.com/api/trade2/search/Rise%20of%20the%20Abyssal"
SEARCH_HEADERS = {"content-type": "application/json"}
FETCH_URL = "https://www.pathofexile.com/api/trade2/fetch/"
NOT_FOUND = "%NOT_FOUND%"
def get_script_dir() -> str:
"""Returns the directory where the script is located."""
return os.path.dirname(os.path.realpath(__file__))
class ImageProvider:
def __init__(self, mode: MODE):
self.mode: MODE = mode
self.net: RateLimiter = RateLimiter(debug=False)
self.cache: dict[str, str] = self.get_cache()
# backup prior cache
self.save_cache(self.cache, output_file=OLD_ITEM_IMAGE_CACHE)
def get_cache(self) -> dict[str, str]:
cache_path = f"{get_script_dir()}/{ITEM_IMAGE_CACHE}"
# Check if the file exists and create it if it doesn't
if not os.path.exists(cache_path):
with open(cache_path, "w", encoding="utf-8") as f:
json.dump({}, f)
# Read and return the contents of the file
with open(cache_path, "r", encoding="utf-8") as f:
return json.loads(f.read())
def save_cache(self, cache: dict[str, str], output_file: str = ITEM_IMAGE_CACHE):
cache_path = f"{get_script_dir()}/{output_file}"
logger.info(f"Saving cache to {cache_path}")
with open(cache_path, "w", encoding="utf-8") as f:
json.dump(cache, f, indent=4)
def apply_images(self, items: pd.DataFrame) -> pd.DataFrame:
item_images = items.copy()
item_images["icon"] = item_images.progress_apply(self.get_image, axis=1) # pyright:ignore [reportUnknownMemberType, reportCallIssue]
self.save_cache(self.cache)
return item_images
def get_image(self, row: pd.Series) -> str:
item = ItemImage(row["refName"], row["namespace"], row["unique"])
save_name = item.save_name()
image_url = NOT_FOUND
if self.mode == "noLookup":
return NOT_FOUND if save_name not in self.cache else self.cache[save_name]
if (
self.mode == "all"
or save_name
not in self.cache # mode = new (or missing and it isn't in cache)
or (self.mode == "missing" and self.cache[save_name] == NOT_FOUND)
):
image_url = self.get_image_url(item)
self.cache[save_name] = image_url if image_url is not None else NOT_FOUND
elif save_name in self.cache:
image_url = self.cache[save_name]
return image_url if image_url is not None else NOT_FOUND
def get_fetch_id(self, item: ItemImage) -> str | None:
payload = item.search_payload()
for _ in range(3):
result = self.net.post(SEARCH_URL, payload=payload, headers=SEARCH_HEADERS) # pyright:ignore [reportArgumentType]
if result.status_code == 200:
data = result.json()
if data.get("result") and len(data.get("result")) > 0:
return data.get("result")[0]
else:
return None
elif result.status_code != 429:
raise Exception(
f"Unexpected status code: {result.status_code}\n {result.text}"
)
raise Exception(f"Retry limit exceeded for {item.name}")
def fetch_listing(self, fetch_id: str):
for _ in range(3):
result = self.net.get(FETCH_URL + fetch_id)
if result.status_code == 200:
data = result.json()
if data.get("result") and len(data.get("result")) > 0:
return data.get("result")[0]
else:
return None
elif result.status_code != 429:
raise Exception(
f"Unexpected status code: {result.status_code}\n {result.text}"
)
raise Exception(f"Retry limit exceeded for {fetch_id}")
def parse_listing_for_url(self, listing) -> str | None:
item = listing.get("item")
if item is None:
return None
icon_url = item.get("icon")
if icon_url is None:
return None
if not icon_url.startswith(
"https://web.poecdn.com/gen/image"
) or not icon_url.endswith(".png"):
raise Exception(f"Unexpected icon url: {icon_url}")
return icon_url
def get_image_url(self, item: ItemImage) -> str | None:
fetch_id = self.get_fetch_id(item)
if fetch_id is None:
return None
listing = self.fetch_listing(fetch_id)
if listing is None:
return None
icon_url = self.parse_listing_for_url(listing)
if icon_url is None:
return None
return icon_url

View File

@@ -0,0 +1,49 @@
from math import exp
import pandas as pd
from constants.filenames import EXPEDITION_FACTIONS
from constants.lang import ENGLISH, LANG
from stores.game_store import GameStore
class LogbookFactions:
def __init__(self, lang: LANG):
self.lang: LANG = lang
self.game_store: GameStore = GameStore(lang)
self.ref_game_store: GameStore = GameStore(ENGLISH)
def create_ref_string(self, name: str) -> str:
return f"Has Logbook Faction: {name}"
def create_matcher(self, name: str) -> list[dict[str, str]]:
return [{"string": name}]
def create_trade_entry(self, id: str) -> dict[str, None | dict[str, list[str]]]:
# return {"ids": {"pseudo": ["pseudo.pseudo_" + id]}}
return {"ids": None}
def create_id(self, name: str) -> str:
return name.lower().replace(" ", "_")
def get_out_factions(self) -> pd.DataFrame:
expedition_factions = self.game_store.get(EXPEDITION_FACTIONS).drop(
columns=["_index", "Id"]
)
ref_expedition_factions = self.ref_game_store.get(EXPEDITION_FACTIONS)
expedition_factions["ref"] = ref_expedition_factions["Name"].apply(
self.create_ref_string
)
expedition_factions["better"] = 0
expedition_factions["matchers"] = expedition_factions["Name"].apply(
self.create_matcher
)
expedition_factions["id"] = ref_expedition_factions["Name"].apply(
self.create_id
)
expedition_factions["trade"] = expedition_factions["id"].apply(
self.create_trade_entry
)
expedition_factions.drop(columns=["Name"], inplace=True)
return expedition_factions

View File

@@ -0,0 +1,474 @@
# pyright: basic
import logging
from typing import Any
import pandas as pd
from pandas.core.frame import DataFrame
from constants.filenames import (
ATLAS_STAT_DESCRIPTIONS,
BASE_ITEM_TYPES,
GOLD_MOD_PRICES,
ITEM_CLASS_CATEGORIES,
ITEM_CLASSES,
MODS,
STATS,
TAGS,
WORDS,
)
from constants.known_stats import (
SAME_TRANSLATIONS_DIFFERENT_STATS,
TRADE_INVERTED,
UNIQUE_ITEMS_FIXED_STATS,
)
from constants.lang import ENGLISH, LANG, RUSSIAN
from constants.mod_types import EXPLICIT, MOD_TYPES
from services.image_provider import MODE, ImageProvider
from services.logbook_factions import LogbookFactions
from services.specific_column_service import SpecificColumnService
from services.value_converter_service import ValueConverterService
from stores.game_store import GameStore
from stores.trade_store import TradeStore
logger = logging.getLogger(__name__)
class NdBuilderService:
local_lookup: dict[str, str]
stats: pd.DataFrame
items: pd.DataFrame
def __init__(self, lang: LANG, image_mode: MODE):
self.lang: LANG = lang
self.ref_trade_store: TradeStore = TradeStore(ENGLISH)
self.trade_store: TradeStore = TradeStore(lang)
self.ref_game_store: GameStore = GameStore(ENGLISH)
self.game_store: GameStore = GameStore(lang)
self.vc = ValueConverterService(lang)
self.image_mode: MODE = image_mode
self.image_provider = ImageProvider(mode=image_mode)
self.specific_column_service = SpecificColumnService(lang)
self.logbook_factions = LogbookFactions(lang)
def build_stats_ndjson(self) -> pd.DataFrame:
logger.info("Building stats")
# Get all required data
# Data from Trade API
logger.info("Importing trade site data")
valueless_trade_stats_df = self.get_output_ready_trade_stats()
value_trade_stats_df = self.get_output_value_stats()
# Data from Descriptions
logger.info("Importing descriptions")
desc_df = self.descriptions_hashed()
logger.info("Joining trade site and descriptions")
joined_df = self.join_trade_and_descriptions(valueless_trade_stats_df, desc_df)
timeless_mask = joined_df["ref"].str.startswith(("Small", "Notable"))
joined_df.loc[timeless_mask] = joined_df.loc[timeless_mask].apply(
self.timeless_function, axis=1
)
logger.info("Joining stats with and without values")
concated = pd.concat([joined_df, value_trade_stats_df]).sort_values("ref")
map_ids = self.map_stat_ids()
concated.loc[concated["id"].isin(map_ids), "fromAreaMods"] = True
joined_df_i = self.add_trade_inverted(concated)
logger.info("Filling in missing matchers")
missing_filled = self.fill_in_missing_matchers(joined_df_i)
logger.info("Adding logbook factions")
log_faction = self.logbook_factions.get_out_factions()
log_added = pd.concat([missing_filled, log_faction]).sort_values("ref")
logger.info("Cleaning up stats")
return self.clean_up_ndjson(log_added)
def clean_up_ndjson(self, ndjson: pd.DataFrame) -> pd.DataFrame:
ndjson = ndjson[
[
"ref",
"dp",
"better",
"matchers",
"trade",
"fromAreaMods",
"id",
# Hash is here and dropped later to not copy values of a slice
"hash",
]
]
ndjson["better"] = ndjson["better"].fillna(1).astype(int)
def custom_sort_key(matcher):
# Determine the group number: 0 for no 'negated' and no 'value', 1 for 'negated', 2 for 'value'
if "negate" not in matcher and "value" not in matcher:
group = 0
elif "negate" in matcher:
group = 1
elif "value" in matcher:
group = 2
else:
# Handles unexpected cases, if any
group = 3
# Return a tuple: group number and string length
return (group, len(matcher["string"]))
ndjson["matchers"] = ndjson["matchers"].apply(
lambda x: sorted(x, key=custom_sort_key)
)
self.stats = ndjson.sort_values(by="ref").drop(columns=["hash"])
return self.stats
def join_trade_and_descriptions(
self, trade_stats_df: pd.DataFrame, desc_df: pd.DataFrame
) -> pd.DataFrame:
def custom_agg(col: pd.Series):
col_name = col.name
if col_name == "hash":
return list(col.dropna())
if col_name == "matchers":
return list(
{
frozenset(d.items()): d for sub in col.dropna() for d in sub
}.values()
)
return col.iloc[0]
grouped_trade_stats_df_xploded = trade_stats_df.explode("hash")
merged_trade_stats_df = grouped_trade_stats_df_xploded.merge(
desc_df, left_on="hash", right_on="hash", how="left"
)
merged_trade_stats_df_un_exploded = (
merged_trade_stats_df.groupby("ref").agg(custom_agg).reset_index()
)
return merged_trade_stats_df_un_exploded
def add_trade_inverted(self, in_df: pd.DataFrame) -> pd.DataFrame:
def add_inverted(row):
trade = row["trade"]
if len(row["hash"]) > 0 and row["hash"][0] in TRADE_INVERTED:
trade["inverted"] = True
return trade
df = in_df.copy()
df["trade"] = df.apply(add_inverted, axis=1)
return df
def stats_combined_df(self) -> pd.DataFrame:
ref_stats_df = self.ref_trade_store.stats()[["id", "text", "type"]]
lang_stats_df = self.trade_store.stats()[["id", "text"]]
combined_df = pd.merge(
ref_stats_df,
lang_stats_df,
on="id",
suffixes=("_ref", "_text"),
validate="1:1",
)
combined_df = combined_df.rename(
columns={"text_text": "text", "text_ref": "ref"}
)
out = combined_df[["id", "text", "ref", "type"]]
out["ref"] = out["ref"].str.replace(r" \(.*\)", "", regex=True)
self.local_lookup = {row["ref"]: row["text"] for _, row in out.iterrows()}
return out
def descriptions_hashed(self) -> pd.DataFrame:
all_desc_de_dupped = self.game_store.get_all_descriptions().drop_duplicates(
"id", keep="last"
)
atlas_ones = self.game_store.get_description(ATLAS_STAT_DESCRIPTIONS)
return pd.concat([atlas_ones, all_desc_de_dupped])
def valueless_trade_stats_df(self) -> pd.DataFrame:
stats_df = self.stats_combined_df()
return stats_df[~stats_df["id"].str.contains("\\|")]
def value_trade_stats_df(self) -> pd.DataFrame:
stats_df = self.stats_combined_df()
value_trade_stats_df: DataFrame = stats_df[
stats_df["id"].str.contains("\\|")
].copy()
value_trade_stats_df["value"] = (
value_trade_stats_df["id"].str.split("\\|").str[-1]
)
value_trade_stats_df["id"] = value_trade_stats_df["id"].str.split("\\|").str[0]
value_trade_stats_df["hash"] = value_trade_stats_df.apply(
lambda row: int(row["id"].split("_")[-1]), axis=1
)
return value_trade_stats_df
def group_trade_stats(self, in_df: pd.DataFrame) -> pd.DataFrame:
# Check we have needed columns
assert set(in_df.columns) == {"id", "text", "ref", "type"} # nosec: B101
grouped_trade_stats_df = in_df.pivot_table(
index=["ref"],
columns="type",
values="id",
aggfunc=lambda ids: list(ids),
).reset_index()
for col in MOD_TYPES:
grouped_trade_stats_df[col] = (
grouped_trade_stats_df[col]
.fillna("")
.apply(lambda d: d if isinstance(d, list) else [])
)
assert set(grouped_trade_stats_df.columns) == {"ref"}.union(MOD_TYPES) # nosec: B101
return grouped_trade_stats_df
def add_hash_to_trade_stats(self, in_df: pd.DataFrame) -> pd.DataFrame:
def get_hash(row) -> list[int]:
hashes: set[int] = set()
for t in MOD_TYPES:
for trade_id in row[t]:
num = trade_id.split("_")[-1]
if num.isdigit():
hashes.add(int(num))
return sorted(hashes)
out_df = in_df.copy()
out_df["hash"] = out_df.apply(lambda row: get_hash(row), axis=1)
return out_df
def convert_to_trade_out(self, in_df: pd.DataFrame) -> pd.DataFrame:
def make_trade(row):
ids = {}
for mod in MOD_TYPES:
if row[mod]:
ids[mod] = sorted(row[mod])
if (
mod == EXPLICIT
and self.lang in SAME_TRANSLATIONS_DIFFERENT_STATS
):
for key, related_stats in SAME_TRANSLATIONS_DIFFERENT_STATS[
self.lang
].items():
if key in ids[mod]:
ids[mod].extend(related_stats)
x: dict[str, dict[Any, Any] | bool] = {"ids": ids}
return x
trade_stats_out_closer_df = in_df.copy().drop(columns=MOD_TYPES)
trade_stats_out_closer_df["trade"] = in_df.apply(make_trade, axis=1)
return trade_stats_out_closer_df
def get_output_ready_trade_stats(self) -> pd.DataFrame:
value_less_stats = self.valueless_trade_stats_df()
grouped_valueless_stats = self.group_trade_stats(value_less_stats)
hash_stats = self.add_hash_to_trade_stats(grouped_valueless_stats)
trade_stats = self.convert_to_trade_out(hash_stats)
return trade_stats
def get_output_value_stats(self) -> pd.DataFrame:
value = self.value_trade_stats_df()
return self.vc.convert_value_stats_to_trade(value)
def timeless_function(self, row: pd.Series) -> pd.Series:
if self.local_lookup is None:
raise ValueError("Local lookup is not set")
ref = row["ref"]
matchers = {"string": self.local_lookup[ref]}
row["matchers"] = [matchers]
return row
def build_items_ndjson(self) -> pd.DataFrame:
logger.info("Building items")
logger.info("Getting unique items")
unique_items = self.unique_items()
logger.info("Getting non-unique items")
non_unique_items = self.non_unique_items()
logger.info("Applying gem and armour columns")
col_added = self.specific_column_service.apply_item_specific_columns(
non_unique_items, self.stats
)
logger.info("Joining unique and non-unique dfs")
combined = pd.concat([unique_items, col_added]).reset_index(drop=True)
logger.info("Cleaning up items")
cleaned_items = self.clean_up_items(combined)
logger.info(f"Applying images at level {self.image_mode}")
images_added = self.apply_images(cleaned_items)
images_added.loc[images_added["namespace"] != "UNIQUE", "craftable"] = (
images_added.loc[images_added["namespace"] != "UNIQUE", "category"].apply(
lambda x: {"category": x}
)
)
logger.info("adding static data")
static = self.ref_trade_store.static()
static["icon_s"] = static["image"].apply(lambda x: f"https://web.poecdn.com{x}")
static_items_added = images_added.merge(
static[["text", "icon_s", "tradeTag"]],
left_on="refName",
right_on="text",
how="left",
)
static_items_added.loc[static_items_added["icon_s"].notna(), "icon"] = (
static_items_added["icon_s"]
)
logger.info("Sorting and returning items")
self.items = static_items_added.sort_values(by=["namespace", "refName"])[
[
"name",
"refName",
"namespace",
"unique",
"icon",
"tags",
"tradeTag",
"craftable",
"w",
"h",
"armour",
"gem",
"rune",
]
]
return self.items
def get_items_base_types(self) -> pd.DataFrame:
ref_base_types = self.ref_game_store.get(BASE_ITEM_TYPES)[
["_index", "ItemClass", "Width", "Height", "Name", "DropLevel", "Tags"]
].rename({"Name": "refName", "_index": "internal_index"}, axis=1)
base_types = self.game_store.get(BASE_ITEM_TYPES)[["Name"]]
return base_types.join(ref_base_types, validate="1:1").rename(
{"refName": "type"}, axis=1
)
def unique_items(self) -> pd.DataFrame:
ts_items = self.ref_trade_store.items()
words = self.game_store.get(WORDS)
joined_base = self.get_items_base_types()
unique_mask = ts_items["unique"] == True # noqa: E712
unique_items = ts_items.loc[unique_mask].copy()
unique_items["namespace"] = "UNIQUE"
# Edge case fix: Rename specific items
outdated_names = {
"Sekhema's Resolve": "Sekhema's Resolve Fire",
"The Road Warrior": "The Immortan",
"Byrnabas": "Brynabas",
"Splinter of Lorrata": "Splinter of Loratta",
}
unique_items["name"] = unique_items["name"].replace(outdated_names)
named_unique_items = unique_items.merge(
words[["Text", "Text2"]], left_on="name", right_on="Text", how="left"
)[["type", "namespace", "Text2", "Text"]].rename(
{"Text2": "name", "Text": "refName"}, axis=1
)
named_unique_items["unique"] = named_unique_items.apply(
lambda row: {"base": row["type"]}
if row["refName"] not in UNIQUE_ITEMS_FIXED_STATS
else {
"base": row["type"],
"fixedStats": UNIQUE_ITEMS_FIXED_STATS[row["refName"]],
},
axis=1,
)
combined_unique = joined_base.merge(
named_unique_items, on="type", how="right"
).drop("Name", axis=1)
return combined_unique
def non_unique_items(self) -> pd.DataFrame:
ts_items = self.ref_trade_store.items()
joined_base = self.get_items_base_types()
non_unique_mask = ts_items["unique"] == False # noqa: E712
non_unique_items = ts_items.loc[non_unique_mask].copy()
non_unique_items["namespace"] = non_unique_items["id"].apply(
lambda x: "GEM" if x == "gem" else "ITEM"
)
non_unique_items.loc[
non_unique_items["type"].str.startswith("Uncut"), "namespace"
] = "ITEM"
classes = self.ref_game_store.get(ITEM_CLASSES)[
["_index", "ItemClassCategory"]
].merge(
self.ref_game_store.get(ITEM_CLASS_CATEGORIES).rename(
{"_index": "_index2"}, axis=1
)[["_index2", "Id"]],
left_on="ItemClassCategory",
right_on="_index2",
how="left",
)
combined_non_unique = joined_base.merge(
non_unique_items[["type", "namespace"]], on="type", how="right"
).rename({"type": "refName", "Name": "name"}, axis=1)
combined_non_unique = combined_non_unique.merge(
classes[["_index", "Id"]],
left_on="ItemClass",
right_on="_index",
validate="m:1",
).drop(["_index"], axis=1)
return combined_non_unique
def map_stat_ids(self) -> set[str]:
mods = self.game_store.get(MODS)
gold = self.game_store.get(GOLD_MOD_PRICES)
stats_lookup = self.game_store.get(STATS)["Id"].to_dict()
map_mods = mods.loc[mods["Id"].str.startswith("Map")]
inner = map_mods.merge(gold, left_on="_index", right_on="Mod", how="inner")
stat_col = [f"Stat{i}" for i in range(1, 7)]
all_stats_l = []
for col in stat_col:
all_stats_l.append(inner[col].fillna(-1).astype(int))
all_stats = pd.concat(all_stats_l)
return set(stats_lookup[x] for x in all_stats[all_stats != -1].dropna()) # pyright: ignore[reportReturnType]
def clean_up_items(self, items: pd.DataFrame) -> pd.DataFrame:
tags = self.ref_game_store.get(TAGS)
tags_lookup = tags["Id"].to_dict()
filtered = items.drop(["ItemClass", "type", "DropLevel"], axis=1).rename(
{"Id": "category", "Width": "w", "Height": "h", "Tags": "tags"}, axis=1
)
filtered["tags"] = filtered["tags"].apply(lambda x: [tags_lookup[t] for t in x])
return filtered
def apply_images(self, items: pd.DataFrame) -> pd.DataFrame:
return self.image_provider.apply_images(items)
def fill_in_missing_matchers(self, joined_df_i: pd.DataFrame) -> pd.DataFrame:
trade_stats_df = self.stats_combined_df()
trade_stats_df["matchers"] = trade_stats_df["text"].apply(
lambda x: [{"string": x}]
)
ref_matchers_dict = trade_stats_df.set_index("ref")["matchers"].to_dict()
def update_matchers(row):
if isinstance(row["matchers"], list) and len(row["matchers"]) == 0:
# Check if ref is in the dictionary
if row["ref"] in ref_matchers_dict:
return ref_matchers_dict[row["ref"]]
return row["matchers"]
joined_df_i["matchers"] = joined_df_i.apply(update_matchers, axis=1)
return joined_df_i

View File

@@ -0,0 +1,265 @@
import json
import logging
import math
from collections import defaultdict
from datetime import datetime
from time import sleep
import cloudscraper
from requests.models import CaseInsensitiveDict, Response
# Initial logging configuration
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
def set_log_level(level):
"""Set the logging level."""
logger.setLevel(level)
logging.getLogger().setLevel(level) # Applies to all loggers
logger.info(f"Log level set to: {level}")
class RateLimitError(Exception):
pass
class RateLimit:
def __init__(self, limit: int, window: int, penalty: int):
self.max = limit
self.window = window
self.penalty = penalty
def __str__(self):
return (
f"RateLimiter<max={self.max}:window={self.window}:penalty={self.penalty}>"
)
def __repr__(self):
return (
f"RateLimiter<max={self.max}:window={self.window}:penalty={self.penalty}>"
)
def __eq__(self, other):
if not isinstance(other, RateLimit):
return False
return (
self.max == other.max
and self.window == other.window
and self.penalty == other.penalty
)
class RateLimiter:
def __init__(self, debug=False):
self.limits = defaultdict()
self.session = cloudscraper.create_scraper(interpreter="nodejs", debug=debug)
def wait(self, duration: int | float):
"""Wait for a specified duration.
Parameters
----------
duration : int | float
The duration to wait in seconds.
"""
assert isinstance(duration, int) or isinstance(duration, float)
assert duration > 0
sleep(duration)
def parse_window_limit(self, window: str) -> RateLimit:
"""Takes a string in the format "limit:interval:penalty" and returns a RateLimit object.
Parameters
----------
window : str
The string to parse.
Returns
-------
RateLimit
The parsed RateLimit object.
"""
assert isinstance(window, str)
split_window = window.split(":")
assert len(split_window) == 3
return RateLimit(
int(split_window[0]),
int(split_window[1]),
int(split_window[2]),
)
def parse_rate_limit(self, limit: str) -> list[RateLimit]:
"""Takes a csv string of rate limits and returns a list of RateLimit objects.
Parameters
----------
limit : str
csv string of rate limits
Returns
-------
list[RateLimit]
The parsed list of RateLimit objects.
"""
assert isinstance(limit, str)
split_limit = limit.split(",")
windows = []
for window in split_limit:
windows.append(self.parse_window_limit(window))
return windows
def get_limits(self, limit: str, state: str) -> list[tuple[RateLimit, RateLimit]]:
"""Takes a group of rate limits and the current state of rate limits from the server and returns a paired list of RateLimit objects.
Parameters
----------
limit : str
csv of rate limits
state : str
csv of the limits but with the current state according to the server
Returns
-------
list[tuple[RateLimit, RateLimit]]
The paired list of RateLimit objects. The first element in the tuple is the limit and the second element is the state.
"""
assert isinstance(limit, str)
assert isinstance(state, str)
# limits follow format: "limit:interval:penalty"
limits = self.parse_rate_limit(limit)
assert isinstance(limits, list)
assert len(limits) > 0
assert isinstance(limits[0], RateLimit)
states = self.parse_rate_limit(state)
assert isinstance(states, list)
assert len(states) > 0
assert isinstance(states[0], RateLimit)
limit_groups = []
for limit_group, state_group in zip(limits, states):
assert limit_group.window == state_group.window
limit_groups.append((limit_group, state_group))
return limit_groups
def wait_if_exceeded(self, limit: RateLimit, state: RateLimit):
"""Waits using time.sleep if the server says we are on a penalty currently. Will wait for the FULL penalty, not just what the server says is remaining.
Parameters
----------
limit : RateLimit
A specific period limit
state : RateLimit
The current state of the limit
"""
assert isinstance(limit, RateLimit)
assert isinstance(state, RateLimit)
assert isinstance(state.penalty, int)
assert isinstance(limit.penalty, int)
# If the server says we are on penalty
if state.penalty > 0:
# Wait for the full penalty
self.wait(limit.penalty)
# It should be safe to assume this will never occur, and thus should raise an error
# Since waiting though, we will just log an error
logger.error(
f"Rate limit exceeded. Waiting for full penalty of {limit.penalty} seconds. RateLimit: {limit}, State: {state}"
)
def wait_if_needed(self, limit: RateLimit, state: RateLimit, policy: str):
assert isinstance(limit, RateLimit)
assert isinstance(state, RateLimit)
assert isinstance(policy, str)
# If we are less than 1.2x the average rate since last update, wait for 1.1x the average rate
average_request_rate = limit.window / limit.max * 2
last_update = self.limits.get(policy, {}).get("last_update")
assert isinstance(last_update, datetime)
time_since_last_update = (datetime.now() - last_update).total_seconds()
if time_since_last_update <= average_request_rate * 1.5:
# Should almost always happen for limit with the longest average request rate
logger.debug(f"Waiting for {average_request_rate * 1.4} | {limit}, {state}")
self.wait(average_request_rate * 1.4)
# If we are close to exceeding this limit, wait 3x the average rate
if state.max >= math.floor(limit.max * 0.9):
logger.warning(
f"Close to exceeding limit, waiting for {average_request_rate * 3} | {limit}, {state}"
)
self.wait(average_request_rate * 3)
def wait_limit(self, policy: str, rules: str, limit: str, state: str):
assert isinstance(policy, str)
assert isinstance(rules, str)
assert isinstance(limit, str)
assert isinstance(state, str)
rate_limits = self.get_limits(limit, state)
self.limits[policy] = {
"policy": policy,
"rules": rules,
"limit": limit,
"state": state,
"rate_limits": rate_limits,
"last_update": datetime.now(),
"prev_state": self.limits.get(policy, {}).get("state"),
"prev_rate_limits": self.limits.get(policy, {}).get("rate_limits"),
}
rate_limits.reverse()
for limit, state in rate_limits:
self.wait_if_exceeded(limit, state)
self.wait_if_needed(limit, state, policy)
logger.debug(f"Current rate limits: {self.limits}")
def handle_headers(self, headers: dict[str, str], status_code: int):
"""Handles the headers returned from the server."""
assert isinstance(headers, CaseInsensitiveDict)
assert isinstance(status_code, int)
if status_code == 429:
logger.error("Rate limit exceeded.")
retry_after = headers.get("Retry-After")
if retry_after:
logger.error(f"Retry after: {retry_after}")
self.wait(int(retry_after))
else:
logger.error("No retry after header found.")
ac_headers = headers.get("access-control-expose-headers")
if isinstance(ac_headers, str):
ac_headers = ac_headers.lower()
if (
"x-rate-limit-policy" in ac_headers
and "x-rate-limit-rules" in ac_headers
and "x-rate-limit-ip" in ac_headers
and "x-rate-limit-ip-state" in ac_headers
):
self.wait_limit(
headers.get("x-rate-limit-policy"),
headers.get("x-rate-limit-rules"),
headers.get("x-rate-limit-ip"),
headers.get("x-rate-limit-ip-state"),
)
else:
logger.error("No access-control-expose-headers header found.")
def get(
self,
url: str,
payload: dict[str, str] | None = None,
headers: dict[str, str] | None = None,
) -> Response:
response = self.session.get(url, json=payload, headers=headers)
self.handle_headers(response.headers, response.status_code)
return response
def post(
self,
url: str,
payload: dict[str, str] | None = None,
headers: dict[str, str] | None = None,
) -> Response:
response = self.session.post(url, json=payload, headers=headers)
self.handle_headers(response.headers, response.status_code)
return response

View File

@@ -0,0 +1,6 @@
from constants.lang import LANG
class RegexService:
def __init__(self, lang: LANG):
self.lang: LANG = lang

View File

@@ -0,0 +1,283 @@
from typing import cast
import pandas as pd
from constants.filenames import (
ARMOUR_TYPES,
ITEM_CLASSES,
SOUL_CORES,
SOUL_CORES_PER_CLASS,
STATS,
)
from constants.lang import ENGLISH, LANG
from stores.game_store import GameStore
GENERAL_CLASS_TO_ITEM_CLASS = {
"armour": [
"Body Armour",
"Boots",
"Buckler",
"Focus",
"Gloves",
"Helmet",
"Shield",
],
"weapon": [
"Bow",
"Claw",
"Crossbow",
"Dagger",
"Flail",
"One Hand Axe",
"One Hand Mace",
"One Hand Sword",
"Spear",
"Two Hand Axe",
"Two Hand Mace",
"Two Hand Sword",
"Warstaff",
"Talisman",
],
"caster": [
"Staff",
"Wand",
],
}
class SpecificColumnService:
def __init__(self, lang: LANG):
self.lang: LANG = lang
# self.ref_trade_store: TradeStore = TradeStore(ENGLISH)
# self.trade_store: TradeStore = TradeStore(lang)
self.ref_game_store: GameStore = GameStore(ENGLISH)
# self.game_store: GameStore = GameStore(lang)
def apply_item_specific_columns(
self, items: pd.DataFrame, stats: pd.DataFrame
) -> pd.DataFrame:
plus_ar = self.apply_armour_column(items)
plus_gem = self.apply_gem_column(plus_ar)
plus_rune = self.apply_rune_column(plus_gem, stats)
return plus_rune
def apply_armour_column(self, items: pd.DataFrame) -> pd.DataFrame:
armour = self.ref_game_store.get(ARMOUR_TYPES)
def create_armour_dict(row: dict[str, int | str]) -> dict[str, list[int]]:
armour_dict: dict[str, list[int]] = {}
if row["Armour"] != 0:
armour_dict["ar"] = [int(row["Armour"]), int(row["Armour"])]
if row["Evasion"] != 0:
armour_dict["ev"] = [int(row["Evasion"]), int(row["Evasion"])]
if row["EnergyShield"] != 0:
armour_dict["es"] = [int(row["EnergyShield"]), int(row["EnergyShield"])]
return armour_dict
armour["armour"] = armour.apply(create_armour_dict, axis=1)
armour = armour[["BaseItemType", "armour"]]
return items.merge(
armour, left_on="internal_index", right_on="BaseItemType", how="left"
).drop("BaseItemType", axis=1)
def apply_gem_column(self, items: pd.DataFrame) -> pd.DataFrame:
gem = items.copy()
def create_gem_dict(namespace_col: str) -> dict[str, bool] | None:
return (
{"awakened": False, "transfigured": False}
if namespace_col == "GEM"
else None
)
gem["gem"] = gem["namespace"].apply(create_gem_dict) # pyright:ignore [reportUnknownMemberType]
return gem
def first_non_negated(self, matchers: list[dict[str, str | int | bool]]):
"""
Returns the first dictionary in 'matchers' list where 'negated' is either absent or False.
:param matchers: List of dictionaries to search.
:return: The first non-negated dictionary or None if all are negated.
"""
sorted_matchers = sorted(
matchers,
key=lambda x: len(x["string"]) if isinstance(x["string"], str) else 9999999,
)
for matcher in sorted_matchers:
# Check for absence of 'negated' key or its value being False
if not matcher.get("negate", False) and not matcher.get("value"):
return matcher
for matcher in sorted_matchers:
if not matcher.get("value"):
return matcher
print(sorted_matchers)
raise ValueError("No non-negated matcher found")
def apply_rune_column(
self, items: pd.DataFrame, stats: pd.DataFrame
) -> pd.DataFrame:
runes_a = self.apply_rune_column_by_rune(items, stats)
runes_b = self.apply_rune_column_by_item_class(items, stats)
runes = pd.concat([runes_a, runes_b])
grouped = runes.groupby("BaseItemType", as_index=False).agg( # pyright:ignore [reportUnknownMemberType]
{"rune": lambda runes: [r for rune_list in runes for r in rune_list]} # pyright:ignore [reportUnknownLambdaType,reportUnknownVariableType]
)
return items.merge(
grouped,
left_on="internal_index",
right_on="BaseItemType",
how="left",
)
def apply_rune_column_by_rune(
self, _: pd.DataFrame, stats: pd.DataFrame
) -> pd.DataFrame:
runes = self.ref_game_store.get(SOUL_CORES).drop(
["_index", "RequiredLevel"], axis=1
)
stats_lookup: dict[int, str] = self.ref_game_store.get(STATS)["Id"].to_dict() # pyright:ignore [reportUnknownVariableType,reportUnknownMemberType]
def replace_indices_with_ids(index_list: list[int]):
return [stats_lookup[i] for i in index_list]
runes["StatsArmour"] = runes["StatsArmour"].apply(replace_indices_with_ids) # pyright:ignore [reportUnknownMemberType]
runes["StatsMartialWeapon"] = runes["StatsMartialWeapon"].apply( # pyright:ignore [reportUnknownMemberType]
replace_indices_with_ids
)
runes["StatsCasterWeapon"] = runes["StatsCasterWeapon"].apply( # pyright:ignore [reportUnknownMemberType]
replace_indices_with_ids
)
runes["StatsAllEquipment"] = runes["StatsAllEquipment"].apply( # pyright:ignore [reportUnknownMemberType]
replace_indices_with_ids
)
def process_row(rune: dict[str, int | list[int]], ref_df: pd.DataFrame):
rune_out: list[
dict[str, str | int | bool | list[int] | list[str] | None]
] = []
def process_stat(stat_list: list[int], value_list_key: str, key: str):
if len(stat_list) > 0:
stat_id = stat_list[0]
# Look up the row in ref_df with the corresponding 'id' and get 'matchers' and 'trade'
row = ref_df.loc[ref_df["id"] == stat_id]
if not row.empty:
matchers = cast(
list[dict[str, str | int | bool]], row["matchers"].iloc[0]
)
trade = (
cast(dict[str, dict[str, list[str]]], row["trade"].iloc[0])
if "trade" in row.columns
else {}
)
# Translate and extract values
translated = self.first_non_negated(matchers).get("string")
trade_id = (
(trade.get("ids") or {}).get("rune") if trade else None
)
# Fill the rune_out dictionary
rune_out.append(
{
"categories": GENERAL_CLASS_TO_ITEM_CLASS.get(key, []),
"string": translated,
"values": cast(list[int], rune[value_list_key]),
"tradeId": trade_id,
}
)
if not (
isinstance(rune["StatsArmour"], list)
and isinstance(rune["StatsMartialWeapon"], list)
and isinstance(rune["StatsCasterWeapon"], list)
and isinstance(rune["StatsAllEquipment"], list)
):
raise ValueError("Stats should be lists")
# Process each type of stat
process_stat(rune["StatsArmour"], "StatsValuesArmour", "armour")
process_stat(
rune["StatsMartialWeapon"], "StatsValuesMartialWeapon", "weapon"
)
process_stat(rune["StatsCasterWeapon"], "StatsValuesCasterWeapon", "caster")
for this_key in GENERAL_CLASS_TO_ITEM_CLASS.keys():
process_stat(
rune["StatsAllEquipment"], "StatsValuesAllEquipment", this_key
)
return rune_out
runes["rune"] = runes.apply(process_row, axis=1, args=(stats,))
filtered_runes = runes[["BaseItemType", "rune"]]
dropped_empty = filtered_runes[
filtered_runes["rune"].apply(lambda x: len(x) > 0) # pyright:ignore [reportUnknownMemberType,reportUnknownArgumentType,reportUnknownLambdaType]
]
return dropped_empty
def apply_rune_column_by_item_class(
self, _: pd.DataFrame, stats: pd.DataFrame
) -> pd.DataFrame:
runes = self.ref_game_store.get(SOUL_CORES_PER_CLASS).drop(["_index"], axis=1)
classes: dict[int, int] = self.ref_game_store.get(ITEM_CLASSES)["Id"].to_dict() # pyright:ignore [reportUnknownVariableType,reportUnknownMemberType]
stats_lookup: dict[int, str] = self.ref_game_store.get(STATS)["Id"].to_dict() # pyright:ignore [reportUnknownVariableType,reportUnknownMemberType]
def replace_indices_with_ids(index_list: list[int]):
return [stats_lookup[i] for i in index_list]
runes["Stats"] = runes["Stats"].apply(replace_indices_with_ids) # pyright:ignore [reportUnknownMemberType]
def process_row(rune: dict[str, int | list[int]], ref_df: pd.DataFrame):
rune_out: list[
dict[str, str | int | bool | list[int] | list[str] | None]
] = []
if not (isinstance(rune["Stats"], list)):
raise ValueError("Stats should be lists")
if len(rune["Stats"]) == 0:
return rune_out
stat_id = rune["Stats"][0]
# Look up the row in ref_df with the corresponding 'id' and get 'matchers' and 'trade'
row = ref_df.loc[ref_df["id"] == stat_id]
if not row.empty:
matchers = cast(
list[dict[str, str | int | bool]], row["matchers"].iloc[0]
)
trade = (
cast(dict[str, dict[str, list[str]]], row["trade"].iloc[0])
if "trade" in row.columns
else {}
)
# Translate and extract values
translated = self.first_non_negated(matchers).get("string")
trade_id = (trade.get("ids") or {}).get("rune") if trade else None
if not isinstance(rune["ItemClass"], int):
raise ValueError("ItemClass should be an integer")
# Fill the rune_out dictionary
rune_out.append(
{
"categories": classes[rune["ItemClass"]],
"string": translated,
"values": cast(list[int], rune["StatsValues"]),
"tradeId": trade_id,
}
)
return rune_out
runes["rune"] = runes.apply(process_row, axis=1, args=(stats,))
filtered_runes = runes[["BaseItemType", "rune"]]
dropped_empty = filtered_runes[
filtered_runes["rune"].apply(lambda x: len(x) > 0) # pyright:ignore [reportUnknownMemberType,reportUnknownArgumentType,reportUnknownLambdaType]
]
return dropped_empty

View File

@@ -0,0 +1,157 @@
# pyright: basic
import re
from collections import defaultdict
from typing import Any
import pandas as pd
from constants.filenames import (
ADVANCED_MOD_STAT_DESCRIPTIONS,
BLIGHT_CRAFTING_RECIPES,
BLIGHT_CRAFTING_RESULTS,
PASSIVE_SKILLS,
)
from constants.known_stats import (
ALLOCATES_NOTABLE,
BETTER_LOOKUP,
DISCONNECTED_PASSIVES_AROUND_KEYSTONE,
JEWEL_RADIUS_CHANGE,
JEWEL_RING_RADIUS,
SUPPORTED_VALUES,
TIME_LOST_HISTORIC_JEWEL,
UNIQUE_JEWEL_PLUS_LEVEL,
VALUE_TO_DESCRIPTION_ID,
VALUE_TO_REF,
)
from constants.lang import GERMAN, LANG
from stores.game_store import GameStore
from stores.helpers.description import Description
class ValueConverterService:
def __init__(self, lang: LANG):
self.lang: LANG = lang
self.blight = self.get_blight()
def get_blight(self) -> dict[int, str]:
g = GameStore(self.lang)
passives = g.get(PASSIVE_SKILLS)[["_index", "PassiveSkillGraphId"]]
blight_recipes = g.get(BLIGHT_CRAFTING_RECIPES)[
["BlightCraftingResult", "BlightCraftingItems"]
]
blight_results = g.get(BLIGHT_CRAFTING_RESULTS)[["_index", "PassiveSkill"]]
merged1 = passives.merge(
blight_results, left_on="_index", right_on="PassiveSkill", how="inner"
)
merged2 = merged1.merge(
blight_recipes,
left_on="_index_y",
right_on="BlightCraftingResult",
how="inner",
)
filtered = merged2[["BlightCraftingItems", "PassiveSkillGraphId"]].dropna()
filtered["PassiveSkillGraphId"] = filtered["PassiveSkillGraphId"].astype(int)
records = filtered.to_dict(orient="records")
return {
r["PassiveSkillGraphId"]: ",".join(
str(oil) for oil in r["BlightCraftingItems"]
)
for r in records
}
def convert_value_stats_to_trade(self, in_df: pd.DataFrame) -> pd.DataFrame:
filtered_to_supported = in_df[in_df["hash"].isin(SUPPORTED_VALUES)]
grouped = filtered_to_supported.set_index("hash").groupby(level="hash")
converted = grouped.apply(self.agg_by_hash).reset_index(drop=True)
converted["dp"] = None
converted["hash"] = converted["hash"].apply(lambda x: [int(x)])
return converted[["ref", "dp", "better", "matchers", "trade", "id", "hash"]]
def agg_by_hash(self, in_df: pd.DataFrame) -> pd.DataFrame:
hash: int = int(in_df.iloc[0].name) # pyright: ignore[reportArgumentType]
if hash == JEWEL_RADIUS_CHANGE:
return self.simple_agg(in_df)
elif hash == JEWEL_RING_RADIUS:
return self.simple_agg(in_df)
elif hash == ALLOCATES_NOTABLE:
return self.simple_agg(in_df)
elif hash == TIME_LOST_HISTORIC_JEWEL:
return self.historic_agg(in_df)
elif hash == DISCONNECTED_PASSIVES_AROUND_KEYSTONE:
return self.simple_agg(in_df)
elif hash == UNIQUE_JEWEL_PLUS_LEVEL:
return self.simple_agg(in_df)
return in_df
def simple_agg(self, in_df: pd.DataFrame) -> pd.DataFrame:
id = int(in_df.iloc[0].name) # pyright: ignore[reportArgumentType]
out = {
"ref": VALUE_TO_REF[id],
"id": VALUE_TO_DESCRIPTION_ID[id],
"hash": id,
"better": 1 if id not in BETTER_LOOKUP else BETTER_LOOKUP[id],
}
ids = defaultdict(set)
matchers: list[dict[str, str | int | bool | list[int]]] = []
for row in in_df.itertuples():
ids[row.type].add(row.id)
matcher: dict[str, Any] = {"string": row.text, "value": int(row.value)} # pyright: ignore[reportArgumentType]
if id == ALLOCATES_NOTABLE and int(row.value) in self.blight: # pyright: ignore[reportArgumentType]
matcher["oils"] = self.blight[int(row.value)] # pyright: ignore[reportArgumentType]
if id == UNIQUE_JEWEL_PLUS_LEVEL:
matcher["string"] = matcher["string"].replace("+", "")
matchers.append(matcher)
out["trade"] = {"ids": {k: list(v) for k, v in ids.items()}, "option": True}
matchers.sort(key=lambda x: x["value"])
out["matchers"] = matchers
return pd.DataFrame([out])
def historic_agg(self, in_df: pd.DataFrame) -> pd.DataFrame:
advanced_desc = Description(self.lang, ADVANCED_MOD_STAT_DESCRIPTIONS)
advanced_desc.load()
advanced_df: dict[str, list[dict[str, str]]] = (
advanced_desc.to_dataframe().set_index("id")["matchers"].to_dict()
)
historic_matchers = advanced_df["local_unique_jewel_alternate_tree_version"]
advanced_desc_array = [matcher["string"] for matcher in historic_matchers]
max_value = (
int(in_df["value"].max())
if self.lang != GERMAN
else int(in_df["value"].max()) - 1
)
if len(advanced_desc_array) != max_value:
# error case, just return simple agg
return self.simple_agg(in_df)
out = []
for row in in_df.itertuples():
value = int(row.value) - 1 # pyright: ignore[reportArgumentType]
if self.lang == GERMAN:
value -= 1
out_row = {
"ref": row.ref.split("\n")[0], # pyright: ignore[reportAttributeAccessIssue, reportArgumentType]
"id": VALUE_TO_DESCRIPTION_ID[TIME_LOST_HISTORIC_JEWEL],
"hash": TIME_LOST_HISTORIC_JEWEL,
"better": BETTER_LOOKUP[TIME_LOST_HISTORIC_JEWEL],
"matchers": [
{
"string": re.sub(r"\([^)]*\)", "", advanced_desc_array[value]),
"advanced": advanced_desc_array[value],
}
],
"trade": {
"ids": {
"explicit": [
f"explicit.stat_{TIME_LOST_HISTORIC_JEWEL}|{value + 1}"
]
}
},
}
out.append(out_row)
return pd.DataFrame(out)

View File

@@ -0,0 +1,46 @@
import os
import pandas as pd
from constants.filenames import (
GAME_API_TABLES_TYPE,
STAT_DESCRIPTION_FILES,
STAT_DESCRIPTION_FILES_TYPE,
)
from constants.lang import LANG, LANGUAGES_NAMES
from stores.helpers.description import Description
class GameStore:
lang: LANG
data_dir: str
def __init__(self, lang: LANG):
self.lang = lang
self.data_dir = os.path.join(
os.path.dirname(__file__),
"../../data/vendor/tables",
LANGUAGES_NAMES[self.lang],
)
def get(self, tableFileName: GAME_API_TABLES_TYPE) -> pd.DataFrame:
return pd.read_json(os.path.join(self.data_dir, tableFileName)) # pyright:ignore [reportUnknownMemberType]
def get_description(
self, tableFileName: STAT_DESCRIPTION_FILES_TYPE
) -> pd.DataFrame:
desc = Description(self.lang, tableFileName)
desc.load()
return desc.to_dataframe()
def get_all_descriptions(self) -> pd.DataFrame:
output = None
for desc_file in STAT_DESCRIPTION_FILES:
desc_df = self.get_description(desc_file)
if output is None:
output = desc_df
else:
output = pd.concat([output, desc_df])
if output is None:
raise ValueError("No descriptions found")
return output

View File

@@ -0,0 +1,316 @@
import os
import re
from itertools import groupby
from re import Pattern
import pandas as pd
from numpy.strings import isdigit
from constants.filenames import STAT_DESCRIPTION_FILES_TYPE
from constants.known_stats import (
BETTER_LOOKUP,
RECOVERY_APPLIED_INSTANTLY,
TIME_LOST_HISTORIC_JEWEL,
)
from constants.lang import (
ALL_LANG,
ENGLISH,
LANG,
LANGUAGES_NAMES,
LANGUAGES_NAMES_TO_CODES,
)
from stores.helpers.hash_computer import HashComputer
class Description:
pattern_full: Pattern[str] = re.compile(r"\[([^\|\]]*?)\|([^\]]+)\]")
pattern_trailing: Pattern[str] = re.compile(r"([^\[\|\s]+)\|([^\]]+)\]")
pattern_no_closing: Pattern[str] = re.compile(r"\[([^\|\]]*?)\|([^\]\[]+)")
pattern_missing_left: Pattern[str] = re.compile(r"\|\s*([^\]]+)\]")
def __init__(self, lang: LANG, filename: STAT_DESCRIPTION_FILES_TYPE):
self.lang: LANG = lang
self.data_dir: str = os.path.join(
os.path.dirname(__file__), "../../../data/vendor/files"
)
self.filename: str = filename
self.data: list[str] | None = None
self.hc: HashComputer = HashComputer()
def load(self):
with open(
os.path.join(self.data_dir, self.filename), "r", encoding="utf-16"
) as f:
datalines = f.readlines()
if len(datalines) == 0:
raise ValueError(f"No data found for {self.filename}")
trimmed_datalines = [line.strip() for line in datalines]
filtered_datalines = [
line.replace("\\n", "\\!")
for line in trimmed_datalines
if line != ""
and not line.startswith("no_description")
and not line.startswith("include")
]
real_description_indices = [
idx for idx, line in enumerate(filtered_datalines) if line == "description"
]
if not real_description_indices:
raise ValueError(f"No descriptions found for {self.filename}")
descriptions: list[str] = []
for i, start_idx in enumerate(real_description_indices):
end_idx = (
real_description_indices[i + 1]
if i + 1 < len(real_description_indices)
else len(filtered_datalines)
)
group = "\n".join(filtered_datalines[start_idx + 1 : end_idx]).strip()
if group:
descriptions.append(group)
if not descriptions:
raise ValueError(f"No descriptions found for {self.filename}")
self.data = descriptions
def to_dataframe(self) -> pd.DataFrame:
output: list[dict[str, str | int | list[dict[str, str | int | bool]]]] = []
if self.data is None:
raise ValueError("No data found")
for description in self.data:
parsed = self.parse_description(description)
output.append(parsed)
return pd.DataFrame(output)
def parse_ids(self, ids: str) -> tuple[int, tuple[str, ...]]:
parts = ids.strip().split()
count = int(parts[0])
return count, tuple(parts[1:])
def split_on_lang(
self, description: str
) -> tuple[str, int, dict[ALL_LANG, list[str]]]:
pre_lines = description.split("\n")
if len(pre_lines) < 2:
raise ValueError("Invalid description block.")
lines = [f'lang "{LANGUAGES_NAMES[ENGLISH]}"', *pre_lines[1:]]
ids = pre_lines[0]
matcher_count = int(pre_lines[1])
split: list[tuple[ALL_LANG, list[str]]] = []
for is_lang, group in groupby(lines, lambda line: line.startswith("lang")):
if is_lang:
for line in group:
lang = line.split('"')[1]
if lang not in LANGUAGES_NAMES_TO_CODES:
raise ValueError(f"Invalid language name: {lang}")
split.append((LANGUAGES_NAMES_TO_CODES[lang], []))
else:
if not split:
raise AssertionError("No language found")
for line in group:
if not line.isdigit():
split[-1][1].append(line)
return (
ids,
matcher_count,
{lang: lang_block for lang, lang_block in split},
)
def _apply_pattern_with_limit(
self, pattern: re.Pattern[str], line: str, max_iterations: int = 25
) -> str:
for _ in range(max_iterations):
if not re.search(pattern, line):
break
line = re.sub(pattern, r"\2" if pattern.groups == 2 else r"\1", line)
else:
raise RuntimeError(
f"Exceeded max iterations for pattern: {pattern.pattern}"
)
return line
def replace_attribute_tags(self, line: str) -> str:
line = self._apply_pattern_with_limit(self.pattern_full, line)
line = self._apply_pattern_with_limit(self.pattern_trailing, line)
line = self._apply_pattern_with_limit(self.pattern_no_closing, line)
line = self._apply_pattern_with_limit(self.pattern_missing_left, line)
line = line.replace("[", "").replace("]", "")
return line
def replace_placeholders(self, line: str) -> str:
# NOTE: May need to move to be after combining with trade stuff
# Replace {0}, {0:+d}, {:+d}, etc.
line = re.sub(r"\{(?:[0-9]+)?(?:\:[^}]+)?\}", "#", line)
# Replace bare {} placeholders
line = re.sub(r"\{\}", "#", line)
# Replace %1%, %2%, etc.
line = re.sub(r"%[0-9]+%", "#", line)
return line
def parse_matcher(self, line: str) -> str:
fixed_attribute_tags = self.replace_attribute_tags(line)
fixed_placeholders = self.replace_placeholders(fixed_attribute_tags)
replaced_newlines = fixed_placeholders.replace("\\!", "\n").replace(" \n", "\n")
return replaced_newlines
def parse_line(self, line: str) -> tuple[tuple[str, ...], str, str | None]:
parts = line.strip().split('"')
if len(parts) < 3:
raise ValueError(f"Invalid line format: {line}")
pre_quote = parts[0].strip()
text = self.parse_matcher(parts[1].strip())
if text.startswith("+"):
text = text[1:]
flags = parts[2].strip() if len(parts) > 2 and parts[2].strip() else None
value_placeholders = tuple(pre_quote.split())
return value_placeholders, text, flags
def parse_description(
self, description: str
) -> dict[str, bool | str | int | list[dict[str, str | int | bool]]]:
ids, _, lang_blocks = self.split_on_lang(description)
_, id_strings = self.parse_ids(ids)
missing_lang = self.lang not in lang_blocks
id = self.hc.compute_hash(list(id_strings))
output: dict[str, bool | str | int | list[dict[str, str | int | bool]]] = {
"id": id_strings[0],
"hash": id,
"better": 1 if id not in BETTER_LOOKUP else BETTER_LOOKUP[id],
}
matchers: list[dict[str, str | int | bool]] = []
dp = None
blocks = lang_blocks[ENGLISH] if missing_lang else lang_blocks[self.lang]
for line in blocks:
if "table_only" in line:
continue
value_placeholders, line_string, flags = self.parse_line(line)
value = self.parse_value(value_placeholders, id)
out: dict[str, str | int | bool] = {}
out["string"] = line_string
if value is not None:
if id == RECOVERY_APPLIED_INSTANTLY and value == 100:
# Instant Recovery is a different trade ID so dont include this matcher
continue
out["value"] = value
negate = self.parse_negate(flags)
if negate or self.override_negate(value_placeholders, id):
out["negate"] = True
canonical = self.parse_canonical(flags)
if canonical:
pass
# out["canonical"] = True
decimal = self.parse_dp(flags, line_string)
if decimal and dp is None:
dp = decimal
matchers = [m for m in matchers if m["string"] != line_string]
matchers.append(out)
output["matchers"] = matchers
if dp:
output["dp"] = dp
return output
def parse_value(self, value_placeholders: tuple[str, ...], id: int) -> int | None:
if not value_placeholders or len(value_placeholders) == 0:
return None
index = self.value_index(id)
placeholder = value_placeholders[index]
if placeholder.isdigit() or (
placeholder.startswith("-") and isdigit(placeholder[1:])
):
return int(placeholder)
if id == 2342939473 or id == 4267306471:
# current_energy_shield_%_as_elemental_damage_reduction viper_strike_dual_wield_damage_+%_final
return None
if id == 2055966527 and value_placeholders == ("#", "#", "!0"):
# bleed_on_hit_with_attacks_%
# # # !0 "[Attack|Attacks] cannot cause [Bleeding|Bleeding]"
return 0
mapping = {
"!0": 0,
"100|#": 100,
"#|-100": -100,
"2147483646|#": 2147483646,
"101|#": 100,
"#|1": 1,
"99|100": 100,
"10|19": 1,
"10|#": 10,
"15|24": 1,
"99|#": 100,
"5|#": 1,
}
return mapping.get(placeholder)
def value_index(self, id: int) -> int:
LOOKUP = {
TIME_LOST_HISTORIC_JEWEL: 1,
}
if id in LOOKUP:
return LOOKUP[id]
return 0
def override_negate(
self, value_placeholders: tuple[str, ...], id: int
) -> bool | None:
if not value_placeholders or len(value_placeholders) == 0:
return None
index = self.value_index(id)
placeholder = value_placeholders[index]
if id in {
836936635,
3598623697,
472520716,
1444556985,
3979226081,
2991563371,
1726353460,
}:
return False
return placeholder == "#|-1"
def parse_negate(self, flags: str | None) -> bool | None:
if flags is None:
return None
if "negate" in flags:
return True
return None
def flip_negate(self, negate: bool | None) -> bool | None:
assert negate is not False
return True if negate is None else None
def parse_canonical(self, flags: str | None) -> bool | None:
if flags is None:
return None
if "canonical" in flags:
return True
return None
def parse_dp(self, flags: str | None, line: str) -> bool | None:
if flags is None:
return None
if "divide_by_one_hundred" in flags or (
"per_minute_to_per_second" in flags and "%" not in line
):
return True
return None

View File

@@ -0,0 +1,23 @@
import struct
from murmurhash2 import murmurhash2
class HashComputer:
def __init__(self, salt1: int = 0xC58F1A7B, salt2: int = 0x02312233):
self.salt1: int = salt1
self.salt2: int = salt2
def compute_hash(self, in_id: list[str]) -> int:
h: list[int] = []
for i in in_id:
h.append(self.hash1(i.encode()))
b = b"".join(struct.pack("<I", i) for i in h)
return self.hash2(b)
def hash1(self, b: bytes) -> int:
return murmurhash2(b, seed=self.salt1)
def hash2(self, b: bytes) -> int:
return murmurhash2(b, seed=self.salt2)

View File

@@ -0,0 +1,87 @@
import json
import os
import pandas as pd
from constants.filenames import ITEMS_JSON, STATIC_JSON, STATS_JSON
from constants.lang import LANG
class TradeStore:
def __init__(self, lang: LANG):
self.lang: LANG = lang
self.data_dir: str = os.path.join(
os.path.dirname(__file__), "../../data/json", self.lang
)
def load_file(
self, filename: str
) -> dict[
str, list[dict[str, str | bool | list[dict[str, str | bool | dict[str, bool]]]]]
]:
with open(os.path.join(self.data_dir, filename), "r", encoding="utf-8") as f:
return json.load(f) # pyright:ignore [ reportAny ]
def items(self) -> pd.DataFrame:
rows: list[dict[str, str | bool | None]] = []
data = self.load_file(ITEMS_JSON)
for item in data["result"]:
if not isinstance(item["entries"], list):
raise ValueError("Entries should be a list")
for entry in item["entries"]:
rows.append(
{ # pyright:ignore [ reportArgumentType ]
"id": item["id"],
"label": item["label"],
"type": entry.get("type"),
"text": entry.get("text"),
"name": entry.get("name"),
"unique": entry.get("flags", {}).get("unique", False), # pyright:ignore [reportUnknownMemberType,reportAttributeAccessIssue]
}
)
return pd.DataFrame(rows)
def stats(self) -> pd.DataFrame:
rows: list[dict[str, str | bool]] = []
data = self.load_file(STATS_JSON)
for stat in data["result"]:
if not isinstance(stat["entries"], list):
raise ValueError("Entries should be a list")
for entry in stat["entries"]:
text = entry["text"]
if isinstance(text, str):
text = text.replace("+#%", "#%")
rows.append(
{ # pyright:ignore [ reportArgumentType ]
"id": entry["id"],
"text": text,
"type": entry["type"],
}
)
return pd.DataFrame(rows).drop_duplicates(subset="id", keep="last")
def static(self) -> pd.DataFrame:
rows: list[dict[str, str]] = []
data = self.load_file(STATIC_JSON)
for item in data["result"]:
if not isinstance(item["entries"], list):
raise ValueError("Entries should be a list")
for entry in item["entries"]:
tag: str | None = entry.get("id") # pyright: ignore[reportAssignmentType]
rows.append(
{ # pyright:ignore [ reportArgumentType ]
"type": item["id"],
"tradeTag": tag
if tag is not None
and (
"precursor-tablet" not in tag
and "idol-of-estazunti" not in tag
and "gaze" not in tag
and "waystone" not in tag
)
else None,
"text": entry.get("text"),
"image": entry.get("image"),
}
)
return pd.DataFrame(rows)

View File

@@ -0,0 +1,67 @@
description
3 bleed_on_hit_with_attacks_% attacks_inflict_bleeding_on_hit cannot_cause_bleeding
4
# # !0 "[Attack|Attacks] cannot cause [Bleeding|Bleeding]"
# !0 0 "[Attack|Attacks] cause [Bleeding|Bleeding]"
100|# 0 0 "[Attack|Attacks] cause [Bleeding|Bleeding]"
# 0 0 "[Attack|Attacks] have {0}% chance to cause [Bleeding|Bleeding]" canonical_line
lang "German"
4
# # !0 "[Attack|Angriffe] können kein [Bleeding|Bluten] verursachen"
# !0 0 "[Attack|Angriffe] verursachen [Bleeding|Bluten]"
100|# 0 0 "[Attack|Angriffe] verursachen [Bleeding|Bluten]"
# 0 0 "[Attack|Angriffe] haben {0}% Chance, [Bleeding|Bluten] zu verursachen" canonical_line
lang "Spanish"
4
# # !0 "Los [Attack|ataques] no pueden aplicar [Bleeding|sangrado]"
# !0 0 "Los [Attack|ataques] aplican [Bleeding|sangrado]"
100|# 0 0 "Los [Attack|ataques] aplican [Bleeding|sangrado]"
# 0 0 "Los [Attack|ataques] tienen un {0}% de probabilidad de aplicar [Bleeding|sangrado]" canonical_line
lang "Traditional Chinese"
4
# # !0 "[Attack|攻擊]不能造成[Bleeding|流血]"
# !0 0 "[Attack|攻擊]會造成[Bleeding|流血]"
100|# 0 0 "[Attack|攻擊]會造成[Bleeding|流血]"
# 0 0 "[Attack|攻擊]有 {0}% 機率造成[Bleeding|流血]" canonical_line
lang "Russian"
4
# # !0 "[Attack|Атаки] не могут вызывать [Bleeding|кровотечение]"
# !0 0 "[Attack|Атаки] вызывают [Bleeding|кровотечение]"
100|# 0 0 "[Attack|Атаки] вызывают [Bleeding|кровотечение]"
# 0 0 "[Attack|Атаки] имеют {0}% шанс вызвать [Bleeding|кровотечение]" canonical_line
lang "Korean"
4
# # !0 "[Attack|공격]으로 [Bleeding|출혈] 유발 불가"
# !0 0 "[Attack|공격] 시 [Bleeding|출혈] 유발"
100|# 0 0 "[Attack|공격] 시 [Bleeding|출혈] 유발"
# 0 0 "[Attack|공격] 시 {0}%의 확률로 [Bleeding|출혈] 유발" canonical_line
lang "French"
4
# # !0 "Les [Attack|Attaques] ne peuvent infliger le [Bleeding|Saignement]"
# !0 0 "Les [Attack|Attaques] infligent le [Bleeding|Saignement]"
100|# 0 0 "Les [Attack|Attaques] infligent le [Bleeding|Saignement]"
# 0 0 "Les [Attack|Attaques] ont {0}% de chances d'infliger le [Bleeding|Saignement]" canonical_line
lang "Portuguese"
4
# # !0 "Ataques não podem causar Sangramento"
# !0 0 "Ataques causam Sangramento"
100|# 0 0 "Ataques causam Sangramento"
# 0 0 "Ataques têm {0}% de chance de causar Sangramento" canonical_line
lang "Thai"
4
# # !0 "การ[Attack|โจมตี] ไม่สามารถสร้างสถานะ [Bleeding|เลือดไหล] ได้"
# !0 0 "การ[Attack|โจมตี] สร้างสถานะ [Bleeding|เลือดไหล]"
100|# 0 0 "การ[Attack|โจมตี] สร้างสถานะ [Bleeding|เลือดไหล]"
# 0 0 "การ[Attack|โจมตี] มีโอกาสสร้างสถานะ [Bleeding|เลือดไหล] {0}%" canonical_line
lang "Simplified Chinese"
4
# # !0 "攻击不能导致流血"
# !0 0 "攻击导致流血"
100|# 0 0 "攻击导致流血"
# 0 0 "攻击有 {0}% 的几率导致流血"
lang "Japanese"
4
# # !0 "[Attack|アタック]は[Bleeding|出血]を付与することができない"
# !0 0 "[Attack|アタック]は[Bleeding|出血]を付与する"
100|# 0 0 "[Attack|アタック]は[Bleeding|出血]を付与する"
# 0 0 "[Attack|アタック]は{0}%の確率で[Bleeding|出血]を付与する" canonical_line

View File

@@ -0,0 +1,39 @@
include "Metadata/StatDescriptions/gem_stat_descriptions.csd"
no_identifiers
description
2 off_hand_weapon_minimum_physical_damage off_hand_weapon_maximum_physical_damage
1
# # "{0} to {1} Base Off Hand [Physical|Physical] Damage"
lang "Simplified Chinese"
1
# # "{0} - {1} 基础副手[Physical|物理]伤害"
lang "Japanese"
1
# # "{0}から{1}の基礎オフハンド[Physical|物理]ダメージ"
lang "Thai"
1
# # "ความเสียหาย [Physical|กายภาพ] มือรอง พื้นฐาน {0} ถึง {1}"
lang "Spanish"
1
# # "Inflige de {0} a {1} de daño [Physical|físico] base con la mano secundaria"
lang "French"
1
# # "{0} à {1} Dégâts [Physical|Physiques] de base de main secondaire"
lang "German"
1
# # "{0} bis {1} [Physical|physischer] Nebenhand-Basisschaden"
lang "Russian"
1
# # "От {0} до {1} базового [Physical|физического] урона левой рукой"
lang "Portuguese"
1
# # "{0} a {1} de Dano [Physical|Físico] Base com a Mão Secundaria"
lang "Traditional Chinese"
1
# # "{0} 至 {1} 基礎副手物理傷害"
lang "Korean"
1
# # "{0}~{1} 기본 보조 장비 [Physical|물리] 피해"

View File

@@ -0,0 +1,4 @@
description
1 totem_maximum_all_elemental_resistances_%
1
# "[Totem|Totems] summoned by Supported Skills have {0:+d}% to all [MaximumResistances|Maximum Elemental Resistances]"

View File

@@ -0,0 +1,25 @@
no_description level
no_description item_drop_slots
no_description main_hand_weapon_type
no_description off_hand_weapon_type
no_description current_endurance_charges
no_description current_frenzy_charges
no_description current_power_charges
no_description monster_slain_experience_+%
no_description monster_dropped_item_rarity_+%
no_description monster_dropped_item_quantity_+%
no_description main_hand_quality
no_description off_hand_quality
no_description skill_visual_scale_+%
no_description main_hand_base_weapon_attack_duration_ms
no_description off_hand_base_weapon_attack_duration_ms
no_description main_hand_minimum_attack_distance
no_description off_hand_minimum_attack_distance
no_description main_hand_maximum_attack_distance
no_description off_hand_maximum_attack_distance
no_description chest_item_quantity_+%
no_description map_item_drop_quantity_+%
no_description map_item_drop_rarity_+%
no_description map_tempest_display_prefix
no_description map_tempest_display_suffix

View File

@@ -0,0 +1,126 @@
description
1 number_of_additional_arrows
2
1 "Fires an additional Arrow"
2|# "Fires {0} additional Arrows"
lang "German"
2
1 "Feuert einen zusätzliches Pfeil"
2|# "Feuert {0} zusätzliche Pfeile"
lang "Spanish"
2
1 "Dispara una flecha adicional"
2|# "Dispara {0} flechas adicionales"
lang "French"
2
1 "Tire une Flèche supplémentaire"
2|# "Tire {0} Flèches supplémentaires"
lang "Russian"
2
1 "Выпускает дополнительную стрелу"
2|# "Выпускает {0} дополнительных стрел(-ы)"
lang "Thai"
2
1 "ยิงศร เพิ่มเติม 1 ดอก"
2|# "ยิงศร เพิ่มเติม {0} ดอก"
lang "Traditional Chinese"
2
1 "發射 1 個額外箭矢"
2|# "發射 {0} 個額外箭矢"
lang "Simplified Chinese"
2
1 "额外获得 1 个箭矢"
2|# "额外获得 {0} 个箭矢"
lang "Portuguese"
2
1 "Dispara uma Flecha adicional"
2|# "Dispara {0} Flechas adicionais"
lang "Korean"
2
1 "추가 화살 발사"
2|# "추가 화살 {0}개 발사"
lang "Japanese"
2
1 "追加の矢を1本放つ"
2|# "追加の矢を{0}本放つ"
description
2 active_skill_additional_projectiles_description_mode number_of_additional_projectiles
2
0 1 "Fires an additional [Projectile|Projectile]"
0 2|# "Fires {1} additional [Projectile|Projectiles]"
lang "Simplified Chinese"
2
0 1 "发射 1 个额外[Projectile|投射物]"
0 2|# "发射 {1} 个额外[Projectile|投射物]"
lang "Japanese"
2
0 1 "[Projectile|投射物]を追加で1個放つ"
0 2|# "[Projectile|投射物]を追加で{0}個放つ"
lang "Thai"
2
0 1 "ยิง[Projectile|โพรเจกไทล์] เพิ่มเติม 1 ลูก"
0 2|# "ยิง[Projectile|โพรเจกไทล์] เพิ่มเติม {1} ลูก"
lang "Spanish"
2
0 1 "Dispara un [Projectile|proyectil] adicional"
0 2|# "Dispara {1} [Projectile|proyectiles] adicionales"
lang "French"
2
0 1 "Tire un [Projectile|Projectile] supplémentaire"
0 2|# "Tire {0} [Projectile|Projectiles] supplémentaires"
lang "German"
2
0 1 "Feuert ein zusätzliches [Projectile|Projektil]"
0 2|# "Feuert {1} zusätzliche [Projectile|Projektile]"
lang "Russian"
2
0 1 "Выпускает дополнительный [Projectile|снаряд]"
0 2|# "Выпускает дополнительных [Projectile|снарядов]: {0}"
lang "Portuguese"
2
0 1 "Dispara un [Projectile|Projétil] adicional"
0 2|# "Dispara {0} [Projectile|Projéteis] adicionais"
lang "Traditional Chinese"
2
0 1 "發射 1 個額外投射物"
0 2|# "發射 {0} 個額外投射物"
lang "Korean"
2
0 1 "[Projectile|투사체] 1개 추가 발사"
0 2|# "[Projectile|투사체] {1}개 추가 발사"
description
1 number_of_chains
1
# "[Chain|Chains] {0} Times"
lang "Simplified Chinese"
1
# "[Chain|连锁] {0} 次"
lang "Japanese"
1
# "{0}回[Chain|連鎖]する"
lang "Spanish"
1
# "Se [Chain|encadena] {0} veces"
lang "Portuguese"
1
# "[Chain|Ricocheteia] {0} Vezes"
lang "German"
1
# "[Chain|Verkettet] sich {0} Mal"
lang "Korean"
1
# "{0}회 [Chain|연쇄]"
lang "Thai"
1
# "[Chain|ชิ่ง] {0} ครั้ง"
lang "Traditional Chinese"
1
# "[Chain|連鎖] {0} 次"
lang "French"
1
# "[Chain|Ricoche] {0} fois"
lang "Russian"
1
# "Наносит удар по [Chain|цепи] {0} раз(а)"

View File

@@ -0,0 +1,41 @@
description
2 arsonist_destructive_link_%_of_life_as_fire_damage quality_display_arsonist_is_gem
2
# 0 "Deals additional [Fire] Damage equal to {0:+d}% of [Minion]'s maximum Life"
# # "Deals additional [Fire] Damage equal to {0}% of [Minion]'s maximum Life"
lang "Simplified Chinese"
1
# # "造成相当于[召唤生物]生命上限 {0}% 的额外[火焰]伤害"
lang "Japanese"
2
# 0 "[Minion|ミニオン]の最大ライフの{0:+d}%と同量の追加[Fire|火]ダメージを与える"
# # "[Minion|ミニオン]の最大ライフの{0}%と同量の追加[Fire|火]ダメージを与える"
lang "Thai"
2
# 0 "สร้างความเสียหาย [Fire|ไฟ] เพิ่มเติม เท่ากับ {0:+d}% ของ พลังชีวิตสูงุสดของ[Minion|มิเนียน]"
# # "สร้างความเสียหาย [Fire|ไฟ] เพิ่มเติม เท่ากับ {0}% ของ พลังชีวิตสูงุสดของ[Minion|มิเนียน]"
lang "Spanish"
2
# 0 "Inflige daño de [Fire|fuego] adicional equivalente a {0:+d}% de la vida máxima del [Minion|esbirro]"
# # "Inflige daño de [Fire|fuego] adicional equivalente al {0}% de la vida máxima del [Minion|esbirro]"
lang "Portuguese"
2
# 0 "Causa Dano de [Fire|Fogo] adicional igual a {0:+d}% da Vida máxima do [Minion|Lacaio]"
# # "Causa Dano de [Fire|Fogo] adicional igual a {0}% da Vida máxima do [Minion|Lacaio]"
lang "German"
2
# 0 "Verursacht zusätzlichen [Fire|Feuer]schaden in Höhe von {0:+d}% des maximalen Lebens der [Minion|Kreatur]"
# # "Verursacht zusätzlichen [Fire|Feuer]schaden in Höhe von {0}% des maximalen Lebens der [Minion|Kreatur]"
lang "Korean"
1
# # "[Minion|소환수]의 최대 생명력의 {0}%와 동일한 추가 [Fire|화염] 피해를 줌"
lang "Russian"
2
# 0 "Наносит дополнительный урон от [Fire|огня], равный {0:+d}% от максимума здоровья [Minion|приспешника]"
# # "Наносит дополнительный урон от [Fire|огня], равный {0}% от максимума здоровья [Minion|приспешника]"
lang "Traditional Chinese"
1
# # "造成相當於[Minion|召喚物]最大生命 {0}% 的[Fire|火焰]傷害"
lang "French"
1
# # "Inflige des Dégâts de [Fire|Feu] supplémentaires équivalents à {0}% de la Vie maximale de la [Minion|Créature]"

View File

@@ -0,0 +1,45 @@
description
2 trinity_damage_+%_final_to_grant_per_50_resonance quality_display_trinity_is_gem
3
1|# 0 "{0:+d}% more [ElementalDamage|Elemental Damage] with [Attack|Attacks] per 50\n[Resonance] of any type"
1|# # "{0}% more [ElementalDamage|Elemental Damage] with [Attack|Attacks] per 50\n[Resonance] of any type"
#|-1 # "{0}% less [ElementalDamage|Elemental Damage] with [Attack|Attacks] per 50\n[Resonance] of any type" negate 1
lang "Spanish"
3
1|# 0 "{0:+d}% más de [ElementalDamage|daño elemental] con [Attack|ataques] por cada 50\nde [Resonance|resonancia] de cualquier tipo"
1|# # "{0}% más de [ElementalDamage|daño elemental] con [Attack|ataques] por cada 50\nde [Resonance|resonancia] de cualquier tipo"
#|-1 # "{0}% menos de [ElementalDamage|daño elemental] con [Attack|ataques] por cada 50\nde [Resonance|resonancia] de cualquier tipo" negate 1
lang "Thai"
3
1|# 0 "เพิ่มความเสียหาย [Attack|โจมตี] [ElementalDamage|ธาตุ] อีก {0:+d}% ต่อสถานะ [Resonance|การสั่นพ้อง]ชนิดใดก็ได้ 50 ระดับ"
1|# # "เพิ่มความเสียหาย [Attack|โจมตี] [ElementalDamage|ธาตุ] อีก {0}% ต่อสถานะ [Resonance|การสั่นพ้อง]ชนิดใดก็ได้ 50 ระดับ"
#|-1 # "ลดความเสียหาย [Attack|โจมตี] [ElementalDamage|ธาตุ] อีก {0}% ต่อสถานะ [Resonance|การสั่นพ้อง]ชนิดใดก็ได้ 50 ระดับ" negate 1
lang "Portuguese"
3
1|# 0 "{0:+d}% mais [ElementalDamage|Dano Elemental] com [Attack|Ataques] por cada 50\nde [Resonance|Ressonância] de qualquer tipo"
1|# # "{0}% mais [ElementalDamage|Dano Elemental] com [Attack|Ataques] por cada 50\nde [Resonance|Ressonância] de qualquer tipo"
#|-1 # "{0}% menos [ElementalDamage|Dano Elemental] com [Attack|Ataques] por cada 50\nde [Resonance|Ressonância] de qualquer tipo" negate 1
lang "Traditional Chinese"
2
1|# # "每 50 點任何類型的[Resonance|共鳴]\n增加 {0}% [Attack|攻擊][ElementalDamage|元素傷害]"
#|-1 # "每 50 點任何類型的[Resonance|共鳴]\n減少 {0}% [Attack|攻擊][ElementalDamage|元素傷害]" negate 1
lang "Japanese"
3
1|# 0 "いずれかの種類の[Resonance|レゾナンス]50ごとに[Attack|アタック]による[ElementalDamage|元素ダメージ]が{0:+d}%上昇する"
1|# # "いずれかの種類の[Resonance|レゾナンス]50ごとに[Attack|アタック]による[ElementalDamage|元素ダメージ]が{0}%上昇する"
#|-1 # "いずれかの種類の[Resonance|レゾナンス]50ごとに[Attack|アタック]による[ElementalDamage|元素ダメージ]が{0}%低下する" negate 1
lang "Korean"
3
1|# 0 "유형과 무관하게 [Resonance|공명] 50당 [Attack|공격]으로 주는\n[ElementalDamage|원소 피해] {0:+d}% 증폭"
1|# # "유형과 무관하게 [Resonance|공명] 50당 [Attack|공격]으로 주는\n[ElementalDamage|원소 피해] {0}% 증폭"
#|-1 # "유형과 무관하게 [Resonance|공명] 50당 [Attack|공격]으로 주는\n[ElementalDamage|원소 피해] {0}% 감폭" negate 1
lang "German"
3
1|# 0 "{0:+d}% mehr [ElementalDamage|Elementarschaden] mit [Attack|Angriffen] pro 50\n[Resonance|Resonanz] jedes Typs"
1|# # "{0}% mehr [ElementalDamage|Elementarschaden] mit [Attack|Angriffen] pro 50\n[Resonance|Resonanz] jedes Typs"
#|-1 # "{0}% weniger [ElementalDamage|Elementarschaden] mit [Attack|Angriffen] pro 50\n[Resonance|Resonanz] jedes Typs" negate 1
lang "Russian"
3
1|# 0 "На {0:+d}% больше [ElementalDamage|урона от стихий] [Attack|атаками] за 50\n[Resonance|резонанса] любого типа"
1|# # "На {0}% больше [ElementalDamage|урона от стихий] [Attack|атаками] за 50\n[Resonance|резонанса] любого типа"
#|-1 # "На {0}% меньше [ElementalDamage|урона от стихий] [Attack|атаками] за 50\n[Resonance|резонанса] любого типа" negate 1

View File

@@ -0,0 +1,34 @@
description
1 memory_line_number_of_excursions
1
# "Area contains {0} Temporal Incursions\nTemporal Incursion Portals have their direction reversed"
lang "Korean"
1
# "지역에 시공 기습 {0}개 등장\n시공 기습 포탈의 방향 반전"
lang "French"
1
# "La Zone contient {0} Incursions temporelles\nLa direction des Portails des Incursions temporelles est inversée"
lang "German"
1
# "Gebiet enthält {0} temporale Inkursionen\nDie Richtung von Inkursions-Portalen ist umgekehrt"
lang "Russian"
1
# "В области можно найти временных Вмешательств: {0}\nПорталы временных Вмешательств работают в обратную сторону"
lang "Spanish"
1
# "El área contiene {0} incursiones temporales\nLos portales de las incursiones temporales tienen la dirección invertida"
lang "Thai"
1
# "ด่านมี การย้อนเวลา {0} แห่ง\nประตูมิติย้อนเวลา ถูกสลับทิศทาง"
lang "Portuguese"
1
# "Área contém {0} Incursões Temporais\nPortais da Incursão Temporal têm sua direção revertida"
lang "Traditional Chinese"
1
# "區域含有 {0} 個短暫穿越\n短暫穿越傳送門的方向顛倒"
lang "Japanese"
1
# "エリアにテンポラルインカージョンが{0}個出現する\nテンポラルインカージョンポータルの方向が逆転している"
lang "Simplified Chinese"
1
# "该区域会出现{0}个额外的时空穿越机会\n时空穿越传送门已反转方向"

View File

@@ -0,0 +1,45 @@
description
1 local_display_socketed_gems_skill_effect_duration_+%
2
1|# "Socketed Gems have {0}% increased Skill Effect Duration"
#|-1 "Socketed Gems have {0}% reduced Skill Effect Duration" negate 1
lang "Portuguese"
2
1|# "Gemas Encaixadas têm Duração do Efeito da Habilidade aumentado em {0}%"
#|-1 "Gemas Encaixadas têm Duração do Efeito da Habilidade reduzido em {0}%" negate 1
lang "Traditional Chinese"
2
1|# "此物品插槽中寶石增加 {0}% 技能效果持續時間"
#|-1 "此物品插槽中寶石減少 {0}% 技能效果持續時間" negate 1
lang "Simplified Chinese"
2
1|# "此物品上的技能石延长 {0}% 技能效果持续时间"
#|-1 "此物品上的技能石缩短 {0}% 技能效果持续时间" negate 1
lang "Thai"
2
1|# "หินที่ใส่ มีระยะเวลาส่งผลของสกิล เพิ่มขึ้น {0}%"
#|-1 "หินที่ใส่ มีระยะเวลาส่งผลของสกิล ลดลง {0}%" negate 1
lang "Russian"
2
1|# "Размещённые камни имеют {0}% увеличение длительности эффектов умений"
#|-1 "Размещённые камни имеют {0}% уменьшение длительности эффектов умений" negate 1
lang "French"
2
1|# "Les Gemmes Enchâssées ont {0}% d'Augmentation de la Durée des Effets des Aptitudes"
#|-1 "Les Gemmes Enchâssées ont {0}% de Réduction de la Durée des Effets des Aptitudes" negate 1
lang "German"
2
1|# "Eingefasste Gemmen haben {0}% verlängerte Fertigkeitseffektdauer"
#|-1 "Eingefasste Gemmen haben {0}% verkürzte Fertigkeitseffektdauer" negate 1
lang "Spanish"
2
1|# "Las gemas engarzadas tienen la duración del efecto de la habilidad aumentada un {0}%"
#|-1 "Las gemas engarzadas tienen la duración del efecto de la habilidad reducida un {0}%" negate 1
lang "Korean"
2
1|# "장착된 젬의 스킬 효과 지속시간 {0}% 증가"
#|-1 "장착된 젬의 스킬 효과 지속시간 {0}% 감소" negate 1
lang "Japanese"
2
1|# "ソケットされたジェムのスキル効果持続時間が{0}%増加する"
#|-1 "ソケットされたジェムのスキル効果持続時間が{0}%減少する" negate 1

View File

@@ -0,0 +1,31 @@
description
1 +1_spirit_per_X_evasion_rating_on_body_armour
1
# "+1 to [Spirit] for every {0} [Evasion|Evasion Rating] on Equipped Body Armour"
lang "French"
1
# "+1 d'[Spirit|Esprit] tous les {0} de Score d'[Evasion|Évasion] sur l'armure de torse équipée"
lang "German"
1
# "+1 zu [Spirit|Wille] für jede {0} [Evasion|Ausweichwert] auf der ausgerüsteten Körperrüstung"
lang "Portuguese"
1
# "+1 de [Spirit|Espírito] por cada {0} de [Evasion|Evasão] no Peitoral Equipado"
lang "Thai"
1
# "[Spirit|พลังวิญญาณ] +1 ต่อ [Evasion|อัตราการหลบหลีก] {0} บน เสื้อเกราะที่สวมใส่"
lang "Japanese"
1
# "装備中の鎧の[Evasion|回避力]{0}ごとに[Spirit|スピリット] +1"
lang "Russian"
1
# "+1 к [Spirit|духу] за каждые {0} [Evasion|уклонения] на надетом нательном доспехе"
lang "Spanish"
1
# "+1 al [Spirit|espíritu] por cada {0} de [Evasion|evasión] en la armadura equipada"
lang "Korean"
1
# "장착한 갑옷의 [Evasion|회피] {0}당 [Spirit|정신력] +1"
lang "Traditional Chinese"
1
# "裝備的胸甲每擁有 {0} [Evasion|閃避值],獲得 +1 [Spirit|精魂]"

View File

@@ -0,0 +1,34 @@
description
1 map_players_gain_onslaught_after_opening_a_strongbox_ms
1
# "Strongboxes grant Onslaught for {0} seconds when opened" milliseconds_to_seconds 1
lang "Portuguese"
1
# "Cofres concedem Agressividade por {0} segundos ao serem abertos" milliseconds_to_seconds 1
lang "Russian"
1
# "Ларцы при открытии даруют эффект Боевой раж на {0} секунд(-ы)" milliseconds_to_seconds 1
lang "Traditional Chinese"
1
# "玩家打開保險箱時獲得 {0} 秒猛攻" milliseconds_to_seconds 1
lang "Thai"
1
# "กล่องนิรภัย ให้สถานะ เร่งราวี {0} วินาที เมื่อถูกเปิด" milliseconds_to_seconds 1
lang "German"
1
# "Tresore gewähren bei Öffnung Ansturm für {0} Sekunden" milliseconds_to_seconds 1
lang "Spanish"
1
# "Las cajas fuertes otorgan Fervor durante {0} segundos al abrirse" milliseconds_to_seconds 1
lang "Simplified Chinese"
1
# "玩家打开保险箱时获得 {0} 秒【猛攻】效果" milliseconds_to_seconds 1
lang "French"
1
# "Les Coffres-forts octroient Assaut pendant {0} lorsqu'ils sont ouverts" milliseconds_to_seconds 1
lang "Korean"
1
# "금고가 열리면 {0}초 동안 맹공 적용" milliseconds_to_seconds 1
lang "Japanese"
1
# "ストロングボックスを開けると猛攻が{0}秒間付与される" milliseconds_to_seconds 1

View File

@@ -0,0 +1,65 @@
description
1 %_maximum_life_as_focus
1
# "Enemies have Maximum [Concentration] equal to {0}% of their Maximum Life"
lang "Spanish"
1
# "Los enemigos tienen una [Concentration|concentración] equivalente al {0}% de su vida máxima"
lang "Traditional Chinese"
1
# "敵人有等同於其最大生命 {0}% 的[Concentration|專注]上限"
lang "Korean"
1
# "적이 자신의 생명력의 {0}%와 동일한 최대 [Concentration|집중] 보유"
lang "German"
1
# "Gegner haben maximale [Concentration|Konzentration] in Höhe von {0}% ihres maximalen Lebens"
lang "Russian"
1
# "Максимум [Concentration|концентрации] врагов равен {0}% от их максимума здоровья"
lang "Portuguese"
1
# "Inimigos têm [Concentration|Concentração] Máxima igual a {0}% de sua Vida Máxima"
lang "Thai"
1
# "ศัตรู มี[Concentration|ความระวังตัว]สูงสุด เท่ากับ {0}% ของ พลังชีวิตสูงสุดของมัน"
lang "Japanese"
1
# "敵はその最大ライフの{0}%と同量の[Concentration|コンセントレーション]を持つ"
lang "French"
1
# "Les Ennemis ont une [Concentration|Concentration] égale à {0}% de leur Vie maximale"
lang "Simplified Chinese"
1
# "敌人具有相当于生命上限 {0}% 的专注"
no_description current_power_charges
description
1 %_of_life_regeneration_applies_to_totems
1
# "{}% of your Life Regeneration applies to your [Totem|Totems] aswell"
lang "German"
1
# "{}% Eurer Lebensregeneration werden auch auf Eure [Totem|Totems] angewendet"
lang "Spanish"
1
# "El {}% de tu regeneración de vida también se aplica a tus [Totem|tótems]"
lang "Russian"
1
# "{}% от вашей регенерации здоровья также применяется к вашим [Totem|тотемам]"
lang "Portuguese"
1
# "{}% da sua Regeneração de Vida também se aplica aos seus [Totem|Totens]"
lang "Thai"
1
# "การเติมพลังชีวิตของคุณ {}% ส่งผลต่อ [Totem|โทเทม]ของคุณ เช่นกัน"
lang "Traditional Chinese"
1
# "你 {}% 的生命回復也會施加在你的[Totem|圖騰]"
lang "Korean"
1
# "플레이어 생명력 재생의 {}%가 [Totem|토템]에도 적용"
lang "Japanese"
1
# "プレイヤーのライフ自動回復の{0}%はプレイヤーの[Totem|トーテム]にも適用される"

View File

@@ -0,0 +1,63 @@
description
1 %_maximum_life_as_focus
1
# "Enemies have Maximum [Concentration] equal to {0}% of their Maximum Life"
lang "Spanish"
1
# "Los enemigos tienen una [Concentration|concentración] equivalente al {0}% de su vida máxima"
lang "Traditional Chinese"
1
# "敵人有等同於其最大生命 {0}% 的[Concentration|專注]上限"
lang "Korean"
1
# "적이 자신의 생명력의 {0}%와 동일한 최대 [Concentration|집중] 보유"
lang "German"
1
# "Gegner haben maximale [Concentration|Konzentration] in Höhe von {0}% ihres maximalen Lebens"
lang "Russian"
1
# "Максимум [Concentration|концентрации] врагов равен {0}% от их максимума здоровья"
lang "Portuguese"
1
# "Inimigos têm [Concentration|Concentração] Máxima igual a {0}% de sua Vida Máxima"
lang "Thai"
1
# "ศัตรู มี[Concentration|ความระวังตัว]สูงสุด เท่ากับ {0}% ของ พลังชีวิตสูงสุดของมัน"
lang "Japanese"
1
# "敵はその最大ライフの{0}%と同量の[Concentration|コンセントレーション]を持つ"
lang "French"
1
# "Les Ennemis ont une [Concentration|Concentration] égale à {0}% de leur Vie maximale"
lang "Simplified Chinese"
1
# "敌人具有相当于生命上限 {0}% 的专注"
description
1 %_of_life_regeneration_applies_to_totems
1
# "{}% of your Life Regeneration applies to your [Totem|Totems] aswell"
lang "German"
1
# "{}% Eurer Lebensregeneration werden auch auf Eure [Totem|Totems] angewendet"
lang "Spanish"
1
# "El {}% de tu regeneración de vida también se aplica a tus [Totem|tótems]"
lang "Russian"
1
# "{}% от вашей регенерации здоровья также применяется к вашим [Totem|тотемам]"
lang "Portuguese"
1
# "{}% da sua Regeneração de Vida também se aplica aos seus [Totem|Totens]"
lang "Thai"
1
# "การเติมพลังชีวิตของคุณ {}% ส่งผลต่อ [Totem|โทเทม]ของคุณ เช่นกัน"
lang "Traditional Chinese"
1
# "你 {}% 的生命回復也會施加在你的[Totem|圖騰]"
lang "Korean"
1
# "플레이어 생명력 재생의 {}%가 [Totem|토템]에도 적용"
lang "Japanese"
1
# "プレイヤーのライフ自動回復の{0}%はプレイヤーの[Totem|トーテム]にも適用される"

Some files were not shown because too many files have changed in this diff Show More