mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2025-12-16 13:05:53 +00:00
Compare commits
344 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf7c8f7ac | ||
|
|
9fc0854958 | ||
|
|
fe5dfb6e10 | ||
|
|
4bd7f4eb21 | ||
|
|
706ef2c921 | ||
|
|
a005c1d733 | ||
|
|
eafd795abc | ||
|
|
9416cbbc54 | ||
|
|
0d98945530 | ||
|
|
867bde022a | ||
|
|
b4fd783634 | ||
|
|
c60b93a052 | ||
|
|
3b49e32d9d | ||
|
|
a4c24a9985 | ||
|
|
17899c5c30 | ||
|
|
6a5a603405 | ||
|
|
ae6fe133fe | ||
|
|
3d55b9ac0a | ||
|
|
6d84612b71 | ||
|
|
3bfc3fca2a | ||
|
|
ab98903edf | ||
|
|
57f696918f | ||
|
|
4c0bbf815c | ||
|
|
c2d85a74eb | ||
|
|
9ccec71e1c | ||
|
|
ecd84efa45 | ||
|
|
5c5d075e13 | ||
|
|
f397d67e7e | ||
|
|
2179b55588 | ||
|
|
edb177f012 | ||
|
|
925b43fe1f | ||
|
|
bdd11522b4 | ||
|
|
c853181e00 | ||
|
|
83014075fa | ||
|
|
e4acfb7139 | ||
|
|
8f96d9b59f | ||
|
|
c19849edfd | ||
|
|
40ac84515a | ||
|
|
4cb035de9e | ||
|
|
f7d05d600c | ||
|
|
f0c1b82424 | ||
|
|
16ee1b44c8 | ||
|
|
5b96e7dd5c | ||
|
|
9c7802048d | ||
|
|
262896d69d | ||
|
|
ff2e5e9afe | ||
|
|
de30c0b70e | ||
|
|
c89f058fac | ||
|
|
63e518a532 | ||
|
|
9bcfabcf6a | ||
|
|
75ae6ac704 | ||
|
|
23a409beb4 | ||
|
|
344e09ee30 | ||
|
|
3ed9bface9 | ||
|
|
1b9e674391 | ||
|
|
c3d27edad1 | ||
|
|
1431ce6a06 | ||
|
|
e95ebe6576 | ||
|
|
b83a2ad4ef | ||
|
|
f0f90769ed | ||
|
|
8212d217d4 | ||
|
|
b5ef198494 | ||
|
|
69d729c8e2 | ||
|
|
e90d2cc479 | ||
|
|
0e4df22ccb | ||
|
|
5bb09fc7c7 | ||
|
|
4b0ff4c4d8 | ||
|
|
6533f8cb81 | ||
|
|
0fa2323ecd | ||
|
|
b19f5949a8 | ||
|
|
617391177c | ||
|
|
257161e993 | ||
|
|
b31dbcb5ab | ||
|
|
ab13ffdf15 | ||
|
|
78ae83d2cf | ||
|
|
eb7227afaf | ||
|
|
edb97e84ff | ||
|
|
61b9967bea | ||
|
|
b874962614 | ||
|
|
3c0a533395 | ||
|
|
4297289951 | ||
|
|
4af188c4c3 | ||
|
|
d9902767dd | ||
|
|
22f7e93f04 | ||
|
|
567066a27a | ||
|
|
201dcd4bad | ||
|
|
c937dcd523 | ||
|
|
56c66394f3 | ||
|
|
8b41960f90 | ||
|
|
345d290f9a | ||
|
|
b91583a90e | ||
|
|
f0551bf6fd | ||
|
|
4f542e86a3 | ||
|
|
756103ef04 | ||
|
|
69708f08d9 | ||
|
|
62aaae0bf2 | ||
|
|
7f3b5b8a12 | ||
|
|
f83ffa9e25 | ||
|
|
5ac3c8bd9d | ||
|
|
c9536c3149 | ||
|
|
a344d3e1cf | ||
|
|
a59cd3ef9a | ||
|
|
e094751650 | ||
|
|
3eb7ef60a3 | ||
|
|
97dc5c14c9 | ||
|
|
c369170608 | ||
|
|
0bab4269ef | ||
|
|
3113946138 | ||
|
|
8c31fcbbec | ||
|
|
3b3f80bc53 | ||
|
|
08f766005c | ||
|
|
e50c983988 | ||
|
|
7046bcec08 | ||
|
|
6c5db5e0ca | ||
|
|
754b86eedd | ||
|
|
6a503b563e | ||
|
|
84956a97f5 | ||
|
|
496cc344b5 | ||
|
|
e1db4b16ce | ||
|
|
962231b02a | ||
|
|
ea108c4467 | ||
|
|
a9220171ca | ||
|
|
160fe9811b | ||
|
|
31e22cbfa6 | ||
|
|
a065a4b595 | ||
|
|
993822cb8f | ||
|
|
3792a7dc06 | ||
|
|
b9f469e0d0 | ||
|
|
930f2ad98c | ||
|
|
faba6d52f3 | ||
|
|
31853cc5b3 | ||
|
|
944fc19ba8 | ||
|
|
0cfe20a49a | ||
|
|
f18150c1cf | ||
|
|
37b8ff1c9f | ||
|
|
9b7d89fa5a | ||
|
|
4eafcdc5b6 | ||
|
|
64330bee0b | ||
|
|
b292afa9db | ||
|
|
ebed50f084 | ||
|
|
ecd2d6afa7 | ||
|
|
88aa28574d | ||
|
|
9f4582cb7d | ||
|
|
6c014da832 | ||
|
|
8dae04129b | ||
|
|
ef389554fd | ||
|
|
7c62289019 | ||
|
|
7abeb85016 | ||
|
|
a6f6eb1d4f | ||
|
|
5a34b0f5f8 | ||
|
|
72c3a1a857 | ||
|
|
3732270de9 | ||
|
|
1992e28636 | ||
|
|
5fd444c04f | ||
|
|
f3e1c7fee6 | ||
|
|
cba5ba0b55 | ||
|
|
7782d98460 | ||
|
|
c129260cdd | ||
|
|
f9b0b5a70d | ||
|
|
bbcefcadf0 | ||
|
|
202660ad33 | ||
|
|
81bd9bdeb4 | ||
|
|
d4eed621d4 | ||
|
|
879282f349 | ||
|
|
dc99645f9b | ||
|
|
779f8109f3 | ||
|
|
f3497619b1 | ||
|
|
c823c28ae1 | ||
|
|
3360af1b1c | ||
|
|
f8f7a639a2 | ||
|
|
cb12d4ca7c | ||
|
|
538f4ea6ce | ||
|
|
3b6c214523 | ||
|
|
b30dc760bc | ||
|
|
c6fc79f496 | ||
|
|
2a3a119480 | ||
|
|
428255cf6c | ||
|
|
3cff973d03 | ||
|
|
f927c63ba5 | ||
|
|
07a0840c79 | ||
|
|
94aadc8a7f | ||
|
|
5c2adf6aab | ||
|
|
67ec519f3d | ||
|
|
0b29f09217 | ||
|
|
5efc28e61d | ||
|
|
517be2cf9c | ||
|
|
0930f8f0ba | ||
|
|
022918c5c6 | ||
|
|
4b46f06ae8 | ||
|
|
882bee2cb1 | ||
|
|
3e4cca92eb | ||
|
|
2e2858c56b | ||
|
|
f10fafd523 | ||
|
|
4b11874a8b | ||
|
|
825ce48a11 | ||
|
|
af588273c4 | ||
|
|
fd37bf9b34 | ||
|
|
7ddff1e69a | ||
|
|
fe952b59d0 | ||
|
|
c4fe9a0c5f | ||
|
|
00a7563174 | ||
|
|
2ea75d58dc | ||
|
|
a8516b9977 | ||
|
|
e3bb5ac1ae | ||
|
|
e71c4abdd4 | ||
|
|
8d88acbc92 | ||
|
|
f0545acc73 | ||
|
|
d985abed62 | ||
|
|
9a0f424462 | ||
|
|
c079d9d34b | ||
|
|
e91103cffe | ||
|
|
f476b249b3 | ||
|
|
60168c43ba | ||
|
|
15a8c5f38b | ||
|
|
cc6b3544fb | ||
|
|
af4d474148 | ||
|
|
2d22836fbd | ||
|
|
ecd9494fe7 | ||
|
|
9407e3949f | ||
|
|
8fff1551ef | ||
|
|
75ae110071 | ||
|
|
a1ae212978 | ||
|
|
32b98801c6 | ||
|
|
be1b41a422 | ||
|
|
3824acba34 | ||
|
|
ba16e53846 | ||
|
|
9798e104e8 | ||
|
|
b0ea4ed30c | ||
|
|
25d7775531 | ||
|
|
b98cf9e9d5 | ||
|
|
361d51d61c | ||
|
|
f945161856 | ||
|
|
071c7de099 | ||
|
|
b0ceb4efde | ||
|
|
3a64e003f6 | ||
|
|
988c943372 | ||
|
|
46e3367341 | ||
|
|
8ee8c5ff71 | ||
|
|
fa23ffbb68 | ||
|
|
3644a6fdce | ||
|
|
eac6c54370 | ||
|
|
91f389d118 | ||
|
|
0b53624908 | ||
|
|
50363c70e5 | ||
|
|
83e147b1df | ||
|
|
598ce9f7d9 | ||
|
|
ef22ab671a | ||
|
|
8f8abaee0b | ||
|
|
3739bdbbec | ||
|
|
5959501a14 | ||
|
|
98f56009eb | ||
|
|
d2ffa46892 | ||
|
|
9eb2f6c31d | ||
|
|
5724df7239 | ||
|
|
97750a1c26 | ||
|
|
eea37254b5 | ||
|
|
6aa288aaf0 | ||
|
|
150f78eff3 | ||
|
|
7998f7ba40 | ||
|
|
c89e4f6810 | ||
|
|
acd0484c7a | ||
|
|
7a177c6a28 | ||
|
|
e9395319df | ||
|
|
099bb1a9ad | ||
|
|
744d911603 | ||
|
|
3f7b233c25 | ||
|
|
d71d33b5b9 | ||
|
|
4e358430a9 | ||
|
|
25f10c19b1 | ||
|
|
6ad747919e | ||
|
|
1086ebc4f8 | ||
|
|
6aa0f2a71e | ||
|
|
015bef29f4 | ||
|
|
ffccdb2f85 | ||
|
|
40a5c8b7a8 | ||
|
|
f22646bd77 | ||
|
|
346b5c3f1e | ||
|
|
4a1f0ed541 | ||
|
|
9082490828 | ||
|
|
c8aa8935b2 | ||
|
|
9abbc342d6 | ||
|
|
6234f192e2 | ||
|
|
7af6f40458 | ||
|
|
11b1031f40 | ||
|
|
27162cf6b0 | ||
|
|
77899fd6a7 | ||
|
|
1da7f727f9 | ||
|
|
e19356e97b | ||
|
|
bfc0d209b3 | ||
|
|
75d96fb4b0 | ||
|
|
bc2861f099 | ||
|
|
f5d7ee990f | ||
|
|
3e04f5aa7e | ||
|
|
1058b6f40a | ||
|
|
8814f39112 | ||
|
|
554a271e48 | ||
|
|
a72472d243 | ||
|
|
1a8942c7ec | ||
|
|
61dcd4bcd0 | ||
|
|
e838549ee5 | ||
|
|
5b054b24bb | ||
|
|
92a949c015 | ||
|
|
409b5b34a8 | ||
|
|
32729fe38e | ||
|
|
850fa90455 | ||
|
|
06c396f0cc | ||
|
|
d02f086f19 | ||
|
|
5e9b4f4703 | ||
|
|
b1a8d15f7d | ||
|
|
4951ac9e2d | ||
|
|
ad10eb5f64 | ||
|
|
1afe3ebf0f | ||
|
|
6257a5f257 | ||
|
|
e7f68ffea5 | ||
|
|
c4b84c200d | ||
|
|
5e118f92ea | ||
|
|
835e43a166 | ||
|
|
fd6c194d39 | ||
|
|
81f845df54 | ||
|
|
78b3ed07e5 | ||
|
|
c25192e6fe | ||
|
|
06be6dc2e0 | ||
|
|
a7009fad05 | ||
|
|
285fcba5ab | ||
|
|
2f4956f2c7 | ||
|
|
256ac61ac0 | ||
|
|
85bce61525 | ||
|
|
cfc4d6fc1c | ||
|
|
6083f56c3b | ||
|
|
a1b8ab0f75 | ||
|
|
448c051bb3 | ||
|
|
aac01ca8c6 | ||
|
|
17c66c76c4 | ||
|
|
d28b212f63 | ||
|
|
294ef9d764 | ||
|
|
190dacd8a2 | ||
|
|
d9706290d0 | ||
|
|
adcd434c71 | ||
|
|
1245ef96d6 | ||
|
|
ab93c8b96a | ||
|
|
ab7cb30588 | ||
|
|
f7d686291f | ||
|
|
4b8e2ae8cf | ||
|
|
3450f39381 |
88
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
88
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal 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
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
||||
@@ -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
|
||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
12
.github/workflows/pages.yml
vendored
12
.github/workflows/pages.yml
vendored
@@ -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
23
.github/workflows/test.yml
vendored
Normal 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
4
.gitignore
vendored
@@ -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
57
.pre-commit-config.yaml
Normal 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)$
|
||||
@@ -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.
|
||||
|
||||
34
README.md
34
README.md
@@ -1,22 +1,38 @@
|
||||
#  Exiled Exchange 2
|
||||
#  Exiled Exchange 2
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
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 |
|
||||
| ------------------------------------ | ------------------------------------ | ------------------------------------ | ------------------------------------ |
|
||||
|  |  |  |  |
|
||||
| Gem | Rare | Unique | Currency |
|
||||
| -------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
|  |  |  |  |
|
||||
|
||||
### Development
|
||||
|
||||
@@ -30,4 +46,4 @@ See [DEVELOPING.md](./DEVELOPING.md)
|
||||
- [poeprices.info](https://www.poeprices.info/)
|
||||
- [poe.ninja](https://poe.ninja/)
|
||||
|
||||

|
||||

|
||||
|
||||
178
dataParser/.gitignore
vendored
Normal file
178
dataParser/.gitignore
vendored
Normal 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/
|
||||
37
dataParser/.pre-commit-config.yaml
Normal file
37
dataParser/.pre-commit-config.yaml
Normal 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
15
dataParser/README.md
Normal 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
9
dataParser/copymain.sh
Normal 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."
|
||||
0
dataParser/data/json/cmn-Hant/.gitkeep
Normal file
0
dataParser/data/json/cmn-Hant/.gitkeep
Normal file
0
dataParser/data/json/de/.gitkeep
Normal file
0
dataParser/data/json/de/.gitkeep
Normal file
0
dataParser/data/json/en/.gitkeep
Normal file
0
dataParser/data/json/en/.gitkeep
Normal file
0
dataParser/data/json/es/.gitkeep
Normal file
0
dataParser/data/json/es/.gitkeep
Normal file
0
dataParser/data/json/ja/.gitkeep
Normal file
0
dataParser/data/json/ja/.gitkeep
Normal file
0
dataParser/data/json/ko/.gitkeep
Normal file
0
dataParser/data/json/ko/.gitkeep
Normal file
0
dataParser/data/json/pt/.gitkeep
Normal file
0
dataParser/data/json/pt/.gitkeep
Normal file
0
dataParser/data/json/ru/.gitkeep
Normal file
0
dataParser/data/json/ru/.gitkeep
Normal file
263
dataParser/data/vendor/config.json
vendored
Normal file
263
dataParser/data/vendor/config.json
vendored
Normal 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
0
dataParser/data/vendor/files/.gitkeep
vendored
Normal file
0
dataParser/data/vendor/tables/.gitkeep
vendored
Normal file
0
dataParser/data/vendor/tables/.gitkeep
vendored
Normal file
71
dataParser/ee2-data-builder.code-workspace
Normal file
71
dataParser/ee2-data-builder.code-workspace
Normal 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,
|
||||
}
|
||||
}
|
||||
156
dataParser/output/cmn-Hant/client_strings.js
Normal file
156
dataParser/output/cmn-Hant/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/cmn-Hant/items.ndjson
Normal file
2458
dataParser/output/cmn-Hant/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1719
dataParser/output/cmn-Hant/stats.ndjson
Normal file
1719
dataParser/output/cmn-Hant/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/de/client_strings.js
Normal file
156
dataParser/output/de/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/de/items.ndjson
Normal file
2458
dataParser/output/de/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/de/stats.ndjson
Normal file
1730
dataParser/output/de/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/en/client_strings.js
Normal file
156
dataParser/output/en/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/en/items.ndjson
Normal file
2458
dataParser/output/en/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/en/stats.ndjson
Normal file
1730
dataParser/output/en/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/es/client_strings.js
Normal file
156
dataParser/output/es/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/es/items.ndjson
Normal file
2458
dataParser/output/es/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/es/stats.ndjson
Normal file
1730
dataParser/output/es/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
157
dataParser/output/ja/client_strings.js
Normal file
157
dataParser/output/ja/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/ja/items.ndjson
Normal file
2458
dataParser/output/ja/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/ja/stats.ndjson
Normal file
1730
dataParser/output/ja/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/ko/client_strings.js
Normal file
156
dataParser/output/ko/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/ko/items.ndjson
Normal file
2458
dataParser/output/ko/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/ko/stats.ndjson
Normal file
1730
dataParser/output/ko/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/pt/client_strings.js
Normal file
156
dataParser/output/pt/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/pt/items.ndjson
Normal file
2458
dataParser/output/pt/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/pt/stats.ndjson
Normal file
1730
dataParser/output/pt/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
156
dataParser/output/ru/client_strings.js
Normal file
156
dataParser/output/ru/client_strings.js
Normal 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>.+)$/,
|
||||
}
|
||||
2458
dataParser/output/ru/items.ndjson
Normal file
2458
dataParser/output/ru/items.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
1730
dataParser/output/ru/stats.ndjson
Normal file
1730
dataParser/output/ru/stats.ndjson
Normal file
File diff suppressed because one or more lines are too long
2
dataParser/pyproject.toml
Normal file
2
dataParser/pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = [".", "src"]
|
||||
328
dataParser/src/constants/client_string_data.py
Normal file
328
dataParser/src/constants/client_string_data.py
Normal 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>.+)"
|
||||
),
|
||||
]
|
||||
217
dataParser/src/constants/filenames.py
Normal file
217
dataParser/src/constants/filenames.py
Normal 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"
|
||||
154
dataParser/src/constants/known_stats.py
Normal file
154
dataParser/src/constants/known_stats.py
Normal 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}"],
|
||||
},
|
||||
}
|
||||
49
dataParser/src/constants/lang.py
Normal file
49
dataParser/src/constants/lang.py
Normal 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()
|
||||
}
|
||||
37
dataParser/src/constants/mod_types.py
Normal file
37
dataParser/src/constants/mod_types.py
Normal 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)
|
||||
12
dataParser/src/constants/other.py
Normal file
12
dataParser/src/constants/other.py
Normal file
@@ -0,0 +1,12 @@
|
||||
LOCAL_SUFFIX = " (Local)"
|
||||
|
||||
GENDER_CLIENT_STRINGS_ORDER = [
|
||||
"NS",
|
||||
"MS",
|
||||
"FS",
|
||||
"NP",
|
||||
"O",
|
||||
"ANY",
|
||||
"M",
|
||||
"F",
|
||||
]
|
||||
28
dataParser/src/constants/urls.py
Normal file
28
dataParser/src/constants/urls.py
Normal 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",
|
||||
]
|
||||
42
dataParser/src/contracts/models/base_client_string.py
Normal file
42
dataParser/src/contracts/models/base_client_string.py
Normal 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
|
||||
19
dataParser/src/contracts/models/regex_client_string.py
Normal file
19
dataParser/src/contracts/models/regex_client_string.py
Normal 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
284
dataParser/src/cs.ipynb
Normal 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
125
dataParser/src/main.py
Normal 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]
|
||||
34
dataParser/src/models/array_client_string.py
Normal file
34
dataParser/src/models/array_client_string.py
Normal 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)"
|
||||
63
dataParser/src/models/capture_placeholder_client_string.py
Normal file
63
dataParser/src/models/capture_placeholder_client_string.py
Normal 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})"
|
||||
142
dataParser/src/models/client_string.py
Normal file
142
dataParser/src/models/client_string.py
Normal 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})"
|
||||
42
dataParser/src/models/const_client_string.py
Normal file
42
dataParser/src/models/const_client_string.py
Normal 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})"
|
||||
42
dataParser/src/models/const_regex_client_string.py
Normal file
42
dataParser/src/models/const_regex_client_string.py
Normal 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})"
|
||||
62
dataParser/src/models/item_image.py
Normal file
62
dataParser/src/models/item_image.py
Normal 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}"
|
||||
BIN
dataParser/src/output.ndjson
Normal file
BIN
dataParser/src/output.ndjson
Normal file
Binary file not shown.
1936
dataParser/src/output_i.ndjson
Normal file
1936
dataParser/src/output_i.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
19
dataParser/src/providers/game_api.py
Normal file
19
dataParser/src/providers/game_api.py
Normal 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}")
|
||||
48
dataParser/src/providers/trade_api.py
Normal file
48
dataParser/src/providers/trade_api.py
Normal 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
22297
dataParser/src/scratch.ipynb
Normal file
File diff suppressed because one or more lines are too long
3698
dataParser/src/scratch_item.ipynb
Normal file
3698
dataParser/src/scratch_item.ipynb
Normal file
File diff suppressed because it is too large
Load Diff
54
dataParser/src/services/client_strings_builder.py
Normal file
54
dataParser/src/services/client_strings_builder.py
Normal 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)
|
||||
139
dataParser/src/services/image_provider.py
Normal file
139
dataParser/src/services/image_provider.py
Normal 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
|
||||
49
dataParser/src/services/logbook_factions.py
Normal file
49
dataParser/src/services/logbook_factions.py
Normal 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
|
||||
474
dataParser/src/services/nd_builder_service.py
Normal file
474
dataParser/src/services/nd_builder_service.py
Normal 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
|
||||
265
dataParser/src/services/rate_limiter.py
Normal file
265
dataParser/src/services/rate_limiter.py
Normal 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
|
||||
6
dataParser/src/services/regex_service.py
Normal file
6
dataParser/src/services/regex_service.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from constants.lang import LANG
|
||||
|
||||
|
||||
class RegexService:
|
||||
def __init__(self, lang: LANG):
|
||||
self.lang: LANG = lang
|
||||
283
dataParser/src/services/specific_column_service.py
Normal file
283
dataParser/src/services/specific_column_service.py
Normal 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
|
||||
157
dataParser/src/services/value_converter_service.py
Normal file
157
dataParser/src/services/value_converter_service.py
Normal 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)
|
||||
46
dataParser/src/stores/game_store.py
Normal file
46
dataParser/src/stores/game_store.py
Normal 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
|
||||
316
dataParser/src/stores/helpers/description.py
Normal file
316
dataParser/src/stores/helpers/description.py
Normal 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
|
||||
23
dataParser/src/stores/helpers/hash_computer.py
Normal file
23
dataParser/src/stores/helpers/hash_computer.py
Normal 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)
|
||||
87
dataParser/src/stores/trade_store.py
Normal file
87
dataParser/src/stores/trade_store.py
Normal 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)
|
||||
67
dataParser/tests/data/cases/case_bleeding_edge_case.csd
Normal file
67
dataParser/tests/data/cases/case_bleeding_edge_case.csd
Normal 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
|
||||
39
dataParser/tests/data/cases/case_drop_starting_lines.csd
Normal file
39
dataParser/tests/data/cases/case_drop_starting_lines.csd
Normal 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|물리] 피해"
|
||||
0
dataParser/tests/data/cases/case_empty.csd
Normal file
0
dataParser/tests/data/cases/case_empty.csd
Normal file
4
dataParser/tests/data/cases/case_missing_lang.csd
Normal file
4
dataParser/tests/data/cases/case_missing_lang.csd
Normal 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]"
|
||||
25
dataParser/tests/data/cases/case_only_no_description.csd
Normal file
25
dataParser/tests/data/cases/case_only_no_description.csd
Normal 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
|
||||
126
dataParser/tests/data/cases/case_split_on_real_descriptions.csd
Normal file
126
dataParser/tests/data/cases/case_split_on_real_descriptions.csd
Normal 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} раз(а)"
|
||||
41
dataParser/tests/data/cases/case_uneven_lines_per_lang.csd
Normal file
41
dataParser/tests/data/cases/case_uneven_lines_per_lang.csd
Normal 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]"
|
||||
@@ -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
|
||||
@@ -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时空穿越传送门已反转方向"
|
||||
@@ -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
|
||||
31
dataParser/tests/data/cases/one_description_test.csd
Normal file
31
dataParser/tests/data/cases/one_description_test.csd
Normal 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|精魂]"
|
||||
@@ -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
|
||||
@@ -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|トーテム]にも適用される"
|
||||
63
dataParser/tests/data/cases/two_description_test.csd
Normal file
63
dataParser/tests/data/cases/two_description_test.csd
Normal 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
Reference in New Issue
Block a user