Compare commits

..

17 Commits

Author SHA1 Message Date
kvan7
b5e935f8f7 fix: Fixes all items showing as in dump tab
Fixes #1617, where all listings appear as if in a dump tab. Renames variable since it is no longer an apt description
2025-07-16 17:59:19 -05:00
Alexander Drozdov
af541abe20 Update config.js 2025-06-14 14:20:53 +03:00
Alexander Drozdov
af5c190159 update game data 2025-06-14 13:49:11 +03:00
Alexander Drozdov
ed26736dba bump ci runners 2025-06-05 09:22:55 +03:00
Alexander Drozdov
f591d58fac update regex length 2025-06-05 09:09:10 +03:00
Kvan7
6a014d92b2 Fix CoE url (#1539) 2025-03-21 17:12:00 +02:00
Alexander Drozdov
f2874eb10a idols are always T1 2025-03-01 15:23:10 +02:00
Alexander Drozdov
2b5e4e6c2f fixes #1523 2025-02-23 14:38:42 +02:00
Alexander Drozdov
18b6ea3337 add support for Idols 2025-02-23 13:35:03 +02:00
Alexander Drozdov
d903dbd43f update data for Phrecia 2025-02-23 11:16:40 +02:00
Alexander Drozdov
aecf7c5128 remove another special case 2025-01-11 13:38:20 +02:00
Alexander Drozdov
46403efe96 continue moving files 2025-01-11 13:34:59 +02:00
Alexander Drozdov
e6936f698e remove icon special cases 2025-01-10 13:24:09 +02:00
Alexander Drozdov
5b6632cb7a extract default config into widgets 2025-01-10 09:16:05 +02:00
Alexander Drozdov
0073f3ccd9 continue script setup refactoring 2025-01-06 13:37:58 +02:00
Alexander Drozdov
4c44316816 update ci 2025-01-06 09:21:52 +02:00
Alexander Drozdov
5da9748632 switch main to npm too 2024-11-30 11:35:58 +02:00
452 changed files with 59743 additions and 149232 deletions

View File

@@ -1,88 +0,0 @@
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

View File

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

View File

@@ -34,7 +34,7 @@ jobs:
needs: renderer
strategy:
matrix:
os: [windows-2025, ubuntu-22.04, macos-14]
os: [windows-2022, ubuntu-22.04, macos-13]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

View File

@@ -3,7 +3,7 @@ name: Docs
on:
push:
branches:
- 'master'
- '**'
tags-ignore:
- '**'
paths:
@@ -25,9 +25,8 @@ jobs:
working-directory: ./docs
- run: npx vitepress build
working-directory: ./docs
- uses: actions/configure-pages@v4
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: ./docs/.vitepress/dist
- id: deployment
uses: actions/deploy-pages@v4
- uses: actions/deploy-pages@v4

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

@@ -11,61 +11,33 @@ Note that these 2 both depend on each other, and one cannot run without the othe
The most up-to-date instructions can always be derived from CI:
[.github/workflows/main.yml](https://github.com/Kvan7/exiled-exchange-2/blob/master/.github/workflows/main.yml)
[.github/workflows/main.yml](https://github.com/SnosMe/awakened-poe-trade/blob/master/.github/workflows/main.yml)
Here's what that looks like as of 2023-12-03.
```shell
cd renderer
npm install
npm run make-index-files
npm run dev
yarn install
yarn make-index-files
yarn dev
# In a second shell
cd main
npm install
npm run dev
```
## Formatting
```shell
cd renderer
npm run format
yarn install
yarn dev
```
# How to build
```shell
cd renderer
npm install
npm run make-index-files
npm run build
yarn install
yarn make-index-files
yarn build
cd ../main
npm install
npm run build
yarn build
# We want to sign with a distribution certificate to ensure other users can
# install without errors
CSC_NAME="Certificate name in Keychain" npm run package
CSC_NAME="Certificate name in Keychain" yarn package
```
# How to release a build
1. Commit all changes
2. Bump version in `main/package.json`
3. `npm i` in renderer & main (update `package-lock.json` with new version)
4. `npm run build` in renderer & main
5. Stage & commit bumped version
6. `git push`
7. `git tag vX.X.X`
8. `git push origin vX.X.X`
9. Open release page, create release with tag & title as text of tag & save as draft
# How to build yourself
```shell
sh testUpdate.sh
```
Read the contents of `testUpdate.sh` to understand what it does. Running random scripts from the internet is not recommended so you really should read the code before running it.

View File

@@ -1,38 +1,17 @@
# ![Perfect Jewelers Orb](./renderer/public/images/jeweler.png) Exiled Exchange 2
# ![Awakener's Orb](https://web.poecdn.com/image/Art/2DItems/Currency/TransferOrb.png) Awakened PoE Trade
![GitHub Downloads (specific asset, latest release)](https://img.shields.io/github/downloads/kvan7/exiled-exchange-2/latest/Exiled-Exchange-2-Setup-0.13.3.exe?style=plastic&link=https%3A%2F%2Ftooomm.github.io%2Fgithub-release-stats%2F%3Fusername%3Dkvan7%26repository%3DExiled-Exchange-2)
![GitHub Tag](https://img.shields.io/github/v/tag/kvan7/exiled-exchange-2?style=plastic&label=latest%20version)
![GitHub commits since latest release (branch)](https://img.shields.io/github/commits-since/kvan7/exiled-exchange-2/latest/dev?style=plastic)
[![](https://user-images.githubusercontent.com/4292308/153364874-dde23599-278c-4350-8d86-dadbc4b978b3.svg)](https://somsubhra.github.io/github-release-stats/?username=SnosMe&repository=awakened-poe-trade)
[![](https://user-images.githubusercontent.com/4292308/153364769-e4fe1e82-1bbc-46ac-8a3c-f5a98a5667cc.svg)](https://patreon.com/awakened_poe_trade)
[![](https://user-images.githubusercontent.com/4292308/153364565-7a545d26-e617-4a33-a919-ff90d8feda3d.svg)](https://github.com/SnosMe/awakened-poe-trade/issues/22)
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)
2. Run installer
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`
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>
➡ [Download for Windows & Linux](https://snosme.github.io/awakened-poe-trade/download) ⬅
## Tool showcase
| Gem | Rare | Unique | Currency |
| -------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------ |
| ![Gem Check](./docs/reference-images/GemCheck.png) | ![Rare Check](./docs/reference-images/RareCheck.png) | ![Unique Check](./docs/reference-images/UniqueCheck.png) | ![Currency Check](./docs/reference-images/CurrencyCheck.png) |
| Gem | Rare | Unique | Currency |
|-----|------|--------|----------|
| ![](https://i.imgur.com/LTsH2DZ.png) | ![](https://i.imgur.com/2XL5Wl8.png) | ![](https://i.imgur.com/UTV6prE.png) | ![](https://i.imgur.com/dQ9Sns6.png) |
### Development
@@ -40,10 +19,9 @@ See [DEVELOPING.md](./DEVELOPING.md)
### Acknowledgments
- [awakened-poe-trade](https://github.com/SnosMe/awakened-poe-trade)
- [libuiohook](https://github.com/kwhat/libuiohook)
- [RePoE](https://github.com/brather1ng/RePoE)
- [poeprices.info](https://www.poeprices.info/)
- [poe.ninja](https://poe.ninja/)
![graph](https://i.imgur.com/MATqhv7.png)
![](https://i.imgur.com/MATqhv7.png)

View File

@@ -0,0 +1,28 @@
{
"folders": [
{
"name": "renderer",
"path": "renderer"
},
{
"name": "main",
"path": "main"
},
{
"name": "docs",
"path": "docs"
},
{
"name": "root",
"path": "."
}
],
"settings": {
"files.exclude": {
"main/": true,
"renderer/": true,
"docs/": true,
"dist/": true
}
}
}

178
dataParser/.gitignore vendored
View File

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

View File

@@ -1,37 +0,0 @@
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 ]

View File

@@ -1,15 +0,0 @@
# 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.

View File

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

View File

@@ -1,263 +0,0 @@
{
"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"
]
}
]
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
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()

View File

@@ -1,284 +0,0 @@
{
"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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,48 +0,0 @@
"""
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}"
)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,63 +0,0 @@
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