Compare commits
31 Commits
v0.1.12
...
4e99dfdd92
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e99dfdd92 | |||
| 5d54da9048 | |||
| d96db4e1f0 | |||
| c40712701c | |||
| c9595312ab | |||
| 91e12e4ba1 | |||
| 921a5a3156 | |||
| 2ab32b258c | |||
| 175c1dbaed | |||
| e9bfbfe74f | |||
| 6782cfcd87 | |||
| eaf84ad605 | |||
| 65e3f5b48e | |||
| 9519b92771 | |||
| 6257027a13 | |||
| cde9194ed5 | |||
| 1c8cf13f67 | |||
| 74a8942999 | |||
| 7f8f09a3ac | |||
| 70f93c3d29 | |||
| 4aa2e136b2 | |||
| 3ae30a5263 | |||
| 4da26911fe | |||
| df1daee6ab | |||
| 9d1a26c8e0 | |||
| 8a4970742c | |||
| b50a6a2f95 | |||
| 4646b51459 | |||
| a72b7bc1e1 | |||
| 7aca648396 | |||
| 36129b15d1 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
build/
|
||||
build-stage*
|
||||
.tools/
|
||||
.cache/
|
||||
*.pak
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
129
ACTIONS.md
Normal file
129
ACTIONS.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# ACTIONS.md
|
||||
|
||||
VERSION: 2
|
||||
MODE: machine-first
|
||||
LANG: ru
|
||||
|
||||
ROUTING:
|
||||
- match_user_request_to_action_id: true
|
||||
- if_match: propose_action
|
||||
- if_no_match: ignore_actions_md
|
||||
- if_no_match_user_message: none
|
||||
|
||||
PROPOSE_RULE:
|
||||
- prompt_template: "Приступить к выполнению '{action_id}'?"
|
||||
- require_user_confirmation: true
|
||||
- execute_without_confirmation: false
|
||||
|
||||
EXECUTION_BASELINE:
|
||||
- enforce_agents_md: true
|
||||
- minimal_non_breaking_changes: true
|
||||
- steps_count_range: [3, 7]
|
||||
- before_commit_push: request_user_approval
|
||||
|
||||
REPORT_FORMAT:
|
||||
- done
|
||||
- changed_files
|
||||
- checks
|
||||
- remaining
|
||||
|
||||
ACTIONS:
|
||||
translation:diff:
|
||||
intent: fetch_upstream_english_and_compare_with_ru
|
||||
inputs:
|
||||
- AGENTS.md::Canonical Paths::Upstream English reference
|
||||
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||
plan:
|
||||
- download_upstream_english_into_ignored_cache
|
||||
- compare_en_vs_ru_by_contentuid_and_version
|
||||
- classify_diff_into_missing_changed_stale
|
||||
- write_machine_readable_and_markdown_reports_for_local_review
|
||||
checks:
|
||||
- xml_valid
|
||||
- cache_path_gitignored
|
||||
- local_only_no_ci_workflow_required
|
||||
outputs:
|
||||
- .cache/upstream/english.xml
|
||||
- build/translation-diff/summary.json
|
||||
- build/translation-diff/summary.md
|
||||
- build/translation-diff/candidates.json
|
||||
translation:apply:
|
||||
intent: apply_translation_edits_to_russian_xml
|
||||
inputs:
|
||||
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||
- build/translation-diff/candidates.json
|
||||
- external_edit_file_with_updates
|
||||
plan:
|
||||
- create_temporary_copy_of_russian_xml
|
||||
- load_edit_file_and_temporary_xml
|
||||
- apply_updates_and_optional_new_entries_by_contentuid
|
||||
- write_utf8_bom_xml_to_temporary_copy
|
||||
- validate_temporary_xml_via_separate_script
|
||||
- replace_original_russian_xml_after_successful_validation
|
||||
- report_changed_entries
|
||||
checks:
|
||||
- xml_valid
|
||||
- contentuid_uniqueness_preserved
|
||||
- only_requested_entries_changed
|
||||
outputs:
|
||||
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||
translation:update:
|
||||
intent: sync_ru_translation_with_upstream
|
||||
inputs:
|
||||
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||
- glossary/glossary.normalized.json
|
||||
- AGENTS.md::Canonical Paths::Upstream English reference
|
||||
plan:
|
||||
- refresh_upstream_english_cache
|
||||
- compare_en_vs_ru_by_contentuid_and_version
|
||||
- if_no_diff_report_translation_is_up_to_date_and_stop
|
||||
- else_apply_prepared_edits_to_temporary_russian_copy
|
||||
- validate_temporary_xml_via_separate_script
|
||||
- replace_original_russian_xml_after_successful_validation
|
||||
checks:
|
||||
- xml_valid
|
||||
- glossary_consistency
|
||||
- scope_limited_to_localization_and_allowed_metadata
|
||||
outputs:
|
||||
- message: translation_up_to_date
|
||||
- Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
|
||||
- optional: Mods/DnD 5.5e AIO Russian/meta.lsx (release-only)
|
||||
after_success:
|
||||
- suggest_action: meta:sync-parent
|
||||
reason: "Обновить версию зависимости из родительского мода (актуальный Version64 и связанные поля зависимости)."
|
||||
|
||||
action:report:
|
||||
intent: unified_task_report
|
||||
inputs:
|
||||
- task_context
|
||||
- modified_files
|
||||
- verification_results
|
||||
plan:
|
||||
- summarize_done
|
||||
- list_changed_files
|
||||
- list_checks
|
||||
- list_remaining
|
||||
checks:
|
||||
- concise
|
||||
- factual
|
||||
- no_unverified_claims
|
||||
outputs:
|
||||
- final_user_report
|
||||
meta:sync-parent:
|
||||
intent: sync_dependency_moduleshortdesc_from_parent_meta
|
||||
inputs:
|
||||
- parent_meta_git_url (optional; defaults to upstream)
|
||||
- Mods/DnD 5.5e AIO Russian/meta.lsx
|
||||
plan:
|
||||
- read_parent_moduleinfo_fields
|
||||
- validate_required_fields_folder_md5_name_publishhandle_uuid_version64
|
||||
- update_target_dependencies_moduleshortdesc_fields
|
||||
- validate_xml_structure
|
||||
- report_changed_fields
|
||||
checks:
|
||||
- xml_valid
|
||||
- required_parent_fields_present
|
||||
- only_dependencies_moduleshortdesc_changed
|
||||
outputs:
|
||||
- Mods/DnD 5.5e AIO Russian/meta.lsx
|
||||
|
||||
205
AGENTS.md
205
AGENTS.md
@@ -1,154 +1,91 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Project Overview
|
||||
## General Rules (MUST)
|
||||
|
||||
This repository contains a standalone Russian localization mod for **Baldur's Gate 3**:
|
||||
### Git Collaboration Policy (General)
|
||||
- Commit/push only after explicit user approval.
|
||||
- After approval: commit and push immediately.
|
||||
- Branch switch prompt (`fix/*` or `feat/*`): ask at dialogue start; reuse the explicit user decision for all subsequent fix/feature tasks in the same dialogue.
|
||||
- Pending clarification/approval question: ask once, in a single short message; do not repeat the same pending question in a separate final message.
|
||||
- After finishing work in `fix/*` or `feat/*`: propose either
|
||||
1. creating an MR into `main`, or
|
||||
2. merging to `main` immediately and deleting the `fix/*`/`feat/*` branch.
|
||||
- If push fails: retry up to 2 more times with 3s pause.
|
||||
- Approval prompts for pending actions: short direct phrasing, no soft/opening phrases; response format is mandatory:
|
||||
- binary action: yes/no question.
|
||||
- multiple actions/combinations: numbered options only.
|
||||
|
||||
- Mod name: `DnD 5.5e All-in-One BEYOND Russian Localization`
|
||||
- Mod folder: `Mods/DnD 5.5e AIO Russian`
|
||||
- Base/original mod dependency: `DnD 5.5e All-in-One BEYOND`
|
||||
- Original mod repository: `https://github.com/Yoonmoonsik/dnd55e`
|
||||
- Original dependency UUID: `897914ef-5c96-053c-44af-0be823f895fe`
|
||||
### Cleanup (General)
|
||||
- Do not leave temporary/debug artifacts in repo.
|
||||
- Remove additional debug/temp dirs unless user asked to keep them.
|
||||
|
||||
This repository is for the localization mod only. It must not gain gameplay logic, Script Extender files, or unrelated assets.
|
||||
### Rules Maintenance (General)
|
||||
- For changes to rules files (`AGENTS.md`, `ACTIONS.md`): prefer optimized, compressed edits for AI-agent execution (machine-readable, unambiguous).
|
||||
- Keep rule updates minimal and non-duplicative: merge overlapping points, remove redundancy, preserve intent.
|
||||
|
||||
## Repository Rules
|
||||
### Communication (General)
|
||||
- Project file links in user-facing Markdown: relative paths, `/` separators, spaces encoded as `%20`.
|
||||
|
||||
- Keep the repository source-only.
|
||||
- Do not commit `.pak` artifacts.
|
||||
- Do not commit temporary build outputs.
|
||||
- Do not add gameplay or script content unrelated to localization/release packaging.
|
||||
- Keep the localization folder and metadata consistent with the packaged mod.
|
||||
## Project-Specific Rules (MUST)
|
||||
|
||||
## Current Important Paths
|
||||
### Scope
|
||||
- Repository purpose: standalone Russian localization mod only.
|
||||
- Allowed domain: localization content + packaging/release metadata.
|
||||
- Forbidden: gameplay logic, Script Extender content, unrelated assets.
|
||||
- Keep repository source-only.
|
||||
- Never commit `.pak` or temporary build artifacts.
|
||||
|
||||
### Canonical Paths
|
||||
- Mod sources: `Mods/DnD 5.5e AIO Russian`
|
||||
- Localization XML: `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
||||
- Original English localization reference: `https://github.com/Yoonmoonsik/dnd55e/blob/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml`
|
||||
- Translation glossary reference: `glossary/glossary.normalized.json` — use this as the primary terminology reference when translating
|
||||
- Russian localization: `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
||||
- Mod metadata: `Mods/DnD 5.5e AIO Russian/meta.lsx`
|
||||
- Build script (single source of build truth): `scripts/build.ps1`
|
||||
- CI workflow: `.gitea/workflows/build.yml`
|
||||
- Main build script: `scripts/build.ps1`
|
||||
|
||||
## Build And Release Model
|
||||
|
||||
The authoritative build logic lives in:
|
||||
|
||||
- `scripts/build.ps1`
|
||||
|
||||
The Gitea workflow should stay thin and only:
|
||||
|
||||
1. prepare the workspace
|
||||
2. download `Divine`
|
||||
3. call `scripts/build.ps1`
|
||||
4. publish the release zip for tag builds
|
||||
|
||||
### Current Build Outputs
|
||||
|
||||
The build script produces:
|
||||
|
||||
- `build/DnD 5.5e AIO Russian.pak`
|
||||
- `build/info.json`
|
||||
- `build/DnD 5.5e AIO Russian <tag>.zip` for tagged builds
|
||||
|
||||
The release zip is expected to contain:
|
||||
|
||||
- the built `.pak`
|
||||
- `info.json`
|
||||
|
||||
## Packaging Notes
|
||||
|
||||
The package must contain only the BG3 mod structure under `Mods/...`.
|
||||
|
||||
Verified expected extracted `.pak` structure:
|
||||
- Glossary (primary terminology reference): `glossary/glossary.normalized.json`
|
||||
- Action catalog and command playbooks: `ACTIONS.md`
|
||||
- Upstream English reference: `https://github.com/Yoonmoonsik/dnd55e/blob/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml`
|
||||
|
||||
### Packaging Invariants
|
||||
- `.pak` must contain only BG3 mod structure under `Mods/...`.
|
||||
- Required content in `.pak`:
|
||||
- `Mods/DnD 5.5e AIO Russian/meta.lsx`
|
||||
- `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
||||
- Must not leak into `.pak`: `.git`, `.gitea`, `scripts`, `tools`, `.tools`, `build`, staging dirs.
|
||||
- Staging for packaging must be in `%TEMP%`, not in dot-prefixed repo dirs.
|
||||
|
||||
Do not allow `.git`, `.gitea`, `scripts`, `tools`, `.tools`, `build`, or staging directories into the `.pak`.
|
||||
### Build/CI Contract
|
||||
- CI workflow stays thin:
|
||||
1. prepare workspace
|
||||
2. download Divine
|
||||
3. call `scripts/build.ps1`
|
||||
4. publish tag archive
|
||||
- Expected build outputs:
|
||||
- `build/DnD 5.5e AIO Russian.pak`
|
||||
- `build/info.json`
|
||||
- `build/DnD 5.5e AIO Russian <tag>.zip` (for tag builds)
|
||||
- Release ZIP must include only `.pak` + `info.json`.
|
||||
- CI triggers: tag `v*` and manual dispatch; not every push to `main`.
|
||||
|
||||
## Important Packaging Behavior
|
||||
### Version/Release Rules
|
||||
- Read release version only from `save/region[@id="Config"]/node[@id="root"]/children/node[@id="ModuleInfo"]/attribute[@id="Version64"]` via explicit XML parsing.
|
||||
- `PublishVersion` must not be changed during release preparation.
|
||||
- Release tag must match the source-of-truth version.
|
||||
- Decision logic before tagging:
|
||||
1. If `ModuleInfo/Version64` was manually changed (e.g. BG3 Toolkit), use matching tag and release.
|
||||
2. If `ModuleInfo/Version64` equals latest released version, bump version first (e.g. `scripts/set-version.ps1 -VersionTag <tag>`), commit, then create/push tag.
|
||||
- `scripts/build.ps1` derives release `Version64` from tag and writes it to generated `info.json` and staged `meta.lsx`.
|
||||
|
||||
There is a runner-specific packaging quirk:
|
||||
### info.json Contract
|
||||
- Top-level keys: `Mods`, `MD5`.
|
||||
- Per-mod keys: `Author`, `Name`, `Folder`, `Version`, `Description`, `UUID`, `Created`, `Dependencies`, `Group`.
|
||||
- `Dependencies` is an array of UUIDs.
|
||||
- Current dependency UUID: `897914ef-5c96-053c-44af-0be823f895fe`.
|
||||
|
||||
- `Divine` can produce a broken 48-byte `.pak` on the CI runner depending on the source path.
|
||||
- Current mitigation is implemented in `scripts/build.ps1`.
|
||||
- The script uses staged sources and fallback packaging attempts.
|
||||
- Staging is performed in `%TEMP%`, not in a dot-prefixed directory inside the repo.
|
||||
### Git Collaboration Policy (Project-Specific)
|
||||
- Commit messages and comments: Russian.
|
||||
- Commit message content: what was done (not what should be done).
|
||||
- If changes affect `.pak` contents or build/release flow: propose releasing next version.
|
||||
- For released versions in user-facing messages: provide direct archive link in Markdown format `[version](url)` when derivable (acceptable immediately after tag push, even before CI finishes).
|
||||
|
||||
If packaging breaks again, debug the source path and unpack the resulting `.pak` locally to verify actual contents.
|
||||
|
||||
## Versioning
|
||||
|
||||
Version displayed by BG3ModManager should be derived from the release tag.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- `scripts/build.ps1` derives `Version64` from tags like `v0.1.0`
|
||||
- the computed version is written into:
|
||||
- generated `info.json`
|
||||
- staged `meta.lsx` before packaging
|
||||
|
||||
Do not manually hardcode release versions in the committed `meta.lsx` for each release if CI can derive them from tags.
|
||||
|
||||
## info.json Expectations
|
||||
|
||||
`info.json` is generated during build and should remain aligned with BG3/BG3ModManager expectations.
|
||||
|
||||
Current expected shape:
|
||||
|
||||
- top-level `Mods`
|
||||
- top-level `MD5`
|
||||
- per-mod fields:
|
||||
- `Author`
|
||||
- `Name`
|
||||
- `Folder`
|
||||
- `Version`
|
||||
- `Description`
|
||||
- `UUID`
|
||||
- `Created`
|
||||
- `Dependencies`
|
||||
- `Group`
|
||||
|
||||
Current dependency model:
|
||||
|
||||
- `Dependencies` is an array of dependency UUIDs
|
||||
- current dependency UUID:
|
||||
- `897914ef-5c96-053c-44af-0be823f895fe`
|
||||
|
||||
## CI Trigger Policy
|
||||
|
||||
Current workflow policy:
|
||||
|
||||
- run on tags `v*`
|
||||
- run on manual dispatch
|
||||
- do not run on every push to `main`
|
||||
|
||||
## Git / Collaboration Preferences
|
||||
|
||||
User preference:
|
||||
|
||||
- after making changes, ask for permission before committing
|
||||
- if the user approves, commit and push immediately
|
||||
- for significant changes, propose moving work into a separate branch
|
||||
- feature/fix branches must use the prefix `feat/` or `fix/`
|
||||
- after finishing work in a `feat/` or `fix/` branch, propose merging it back into `main`
|
||||
- comments and commit messages should be written in Russian
|
||||
- commit messages should describe what was done, not what should be done
|
||||
- if changes affect files that go into the final `.pak`, or change the build/release process, propose releasing the next version
|
||||
- if push fails, retry up to two more times with a 3-second pause between attempts
|
||||
|
||||
Do not auto-commit or auto-push without explicit user approval.
|
||||
|
||||
## Cleanup Expectations
|
||||
|
||||
Temporary directories and debug artifacts should not remain in the repository.
|
||||
|
||||
Ignored paths currently include:
|
||||
|
||||
- `build/`
|
||||
- `build-stage*`
|
||||
- `.tools/`
|
||||
- `*.pak`
|
||||
|
||||
If local debugging creates additional temporary folders, remove them when done unless the user explicitly wants to keep them.
|
||||
### Cleanup (Project-Specific)
|
||||
- Ignored/temp patterns include: `build/`, `build-stage*`, `.tools/`, `*.pak`.
|
||||
|
||||
@@ -454,7 +454,7 @@
|
||||
<content contentuid="h2c05bd1dg9548g9d0ag66dbgd31ce3af6dab" version="1">Если вы попадаете по врагу <LSTag Tooltip="OpportunityAttack">атакой по возможности</LSTag>, вы можете снизить скорость существа до 0 до конца текущего хода. А ваша <LSTag Tooltip="MovementSpeed">скорость передвижения</LSTag> увеличивается на [1] в ваш следующий ход.</content>
|
||||
<content contentuid="hd54cf49fg52f9gc82fg23ddg240455666d75" version="1">Враг неумолимого мстителя</content>
|
||||
<content contentuid="h4580d441g9dc0gbc53g7a59g25cce7bd8ba2" version="1">Не может двигаться. Скован <LSTag Tooltip="OpportunityAttack">атакой по возможности</LSTag> от Неумолимого мстителя.</content>
|
||||
<content contentuid="h8a2bbcc7gff7eg7312gb3f5g2e15ba81d07c" version="1">Избранный враг</content>
|
||||
<content contentuid="h8a2bbcc7gff7eg7312gb3f5g2e15ba81d07c" version="2">Метка охотника</content>
|
||||
<content contentuid="hcd8000b2gdb8aga27dg0c5agb3cd6b125346" version="1">Заклинание «Метка охотника» всегда подготовлено. Вы можете сотворять его дважды без траты ячейки заклинания, и восстанавливаете все израсходованные использования этой способности после продолжительного отдыха.<br><br>Количество раз, которое вы можете сотворять это заклинание без ячейки, увеличивается на определённых уровнях следопыта, как показано в столбце «Избранный враг» таблицы особенностей следопыта.</content>
|
||||
<content contentuid="hdfdce3a3g8fd1g0e71gf799gc4b6e93e4494" version="1">Уровень 1: Избранный враг</content>
|
||||
<content contentuid="h306c6ef1gb334gc9d9g3199g2f6fed258ca6" version="1">Заклинание «Метка охотника» всегда подготовлено. Вы можете сотворять его дважды без траты ячейки заклинания, и восстанавливаете все израсходованные использования этой способности после продолжительного отдыха.<br><br>Количество раз, которое вы можете сотворять это заклинание без ячейки, увеличивается на определённых уровнях следопыта, как показано в столбце «Избранный враг» таблицы особенностей следопыта.</content>
|
||||
@@ -615,10 +615,10 @@
|
||||
<content contentuid="ha82b5e6dgb4b1gb2a1g8b53g475515ffec20" version="1">Громовой клинок</content>
|
||||
<content contentuid="h0e37672dgdb7bg5838g12bcg8e96a88d3d98" version="1">Нанесите удар оружием, наделяя противника резонансом, который причинит ему [1] урона при перемещении.</content>
|
||||
<content contentuid="hde7eade7g981dg355bg6e30g0089c6fe0377" version="1">Точный удар (в ближнем бою)</content>
|
||||
<content contentuid="hfc5b991fgffcdgbc3bgbe4eg4bbb47637ce8" version="1">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. При попадании наносится урон излучением.</content>
|
||||
<content contentuid="hfc5b991fgffcdgbc3bgbe4eg4bbb47637ce8" version="2">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. Если атака наносит урон, он может быть уроном излучением или обычным типом урона оружия (на ваш выбор).</content>
|
||||
<content contentuid="hc48575fbg3411gc50ag88aeg18f05fb53a0f" version="1">Точный удар (дальний бой)</content>
|
||||
<content contentuid="he4de88abg8ba4g6afeg8c45g9069fad18699" version="1">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. При попадании наносится урон излучением.</content>
|
||||
<content contentuid="h573020b3g1241g8748g5cccgf1a7c580e5d4" version="1">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. При попадании наносится урон излучением.</content>
|
||||
<content contentuid="he4de88abg8ba4g6afeg8c45g9069fad18699" version="2">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. Если атака наносит урон, он может быть уроном излучением или обычным типом урона оружия (на ваш выбор).</content>
|
||||
<content contentuid="h573020b3g1241g8748g5cccgf1a7c580e5d4" version="2">Руководствуясь вспышкой магического прозрения, вы совершаете одну атаку оружием, использованным при сотворении заклинания. Для бросков атаки и урона используется ваша характеристика сотворения заклинаний вместо Силы или Ловкости. Если атака наносит урон, он может быть уроном излучением или обычным типом урона оружия (на ваш выбор).</content>
|
||||
<content contentuid="h17b4c239g8209gd0fbg57b8gadfb6d1e2826" version="1">Ваш <LSTag Type="Spell" Tooltip="Target_FindFamiliar">фамильяр</LSTag> получает свойство «Дополнительная атака».</content>
|
||||
<content contentuid="h170d4635g2a45gd51dg6f7cg0cc6d58ae805" version="1">Вы можете сотворять заклинания <LSTag Type="Spell" Tooltip="Target_AnimateDead">Оживление мертвецов</LSTag>, <LSTag Type="Spell" Tooltip="Target_Haste">Ускорение</LSTag> и <LSTag Type="Spell" Tooltip="Target_CallLightning">Призыв молнии</LSTag> по одному разу за долгий отдых.</content>
|
||||
<content contentuid="h1b185049g69f6gd479g03c1g85c564525648" version="1">Потусторонняя кара</content>
|
||||
@@ -1492,9 +1492,9 @@
|
||||
<content contentuid="h112e5aabgae68g5b63g8e95g3c2e5fd8ebb8" version="1">Телекинетическое перемещение</content>
|
||||
<content contentuid="h789e4f7cgbbfdgd136g7526g0cb392afcf9d" version="1">Вы можете перемещать объект или существо силой разума. В качестве Магического действия выберите видимую вами цель в пределах 30 футов; ей должен быть незакреплённый объект Большого размера или меньше, либо добровольное существо, кроме вас. Вы перемещаете цель на расстояние до 30 футов в видимое вами свободное пространство. В качестве альтернативы, если цель — Крошечный объект, вы можете переместить его в руку или из неё.</content>
|
||||
<content contentuid="hb9f6e95bg1ecbg4d70g2b47g004a19f989d8" version="1">7-й уровень: Псионический скачок</content>
|
||||
<content contentuid="h53a46d69g6181g8c48g7c48g569803d1df00" version="1">В качестве Бонусного действия вы получаете Скорость полета, равную удвоенной вашей Скорости, до конца текущего хода. После использования этого Бонусного действия вы не сможете использовать его снова, пока не завершите Короткий или Длинный отдых, если только не потратите Кость псионической энергии (действие не требуется) для восстановления возможности его использования.</content>
|
||||
<content contentuid="h53a46d69g6181g8c48g7c48g569803d1df00" version="2">Бонусным действием вы можете перелететь в выбранную точку, затратив всего 10 футов перемещения. Использовав это бонусное действие, вы не сможете сделать это снова, пока не завершите короткий или длительный отдых, если только не потратите Кость псионической энергии (действие не требуется), чтобы восстановить его использование.</content>
|
||||
<content contentuid="h82e5f786g25beg91b9g6a62g3f5142f4ec41" version="1">Псионический скачок</content>
|
||||
<content contentuid="ha42816f3g2b46g7a69gebb9g476dfa5e4f31" version="1">В качестве Бонусного действия вы получаете Скорость полета, равную удвоенной вашей Скорости, до конца текущего хода. После использования этого Бонусного действия вы не сможете использовать его снова, пока не завершите Короткий или Длинный отдых, если только не потратите Кость псионической энергии (действие не требуется) для восстановления возможности его использования.</content>
|
||||
<content contentuid="ha42816f3g2b46g7a69gebb9g476dfa5e4f31" version="2">Бонусным действием вы можете перелететь в выбранную точку, затратив всего 10 футов перемещения. Использовав это бонусное действие, вы не сможете сделать это снова, пока не завершите короткий или длительный отдых, если только не потратите Кость псионической энергии (действие не требуется), чтобы восстановить его использование.</content>
|
||||
<content contentuid="h890ef36dga451ge5acgb49dg6c441e309f11" version="1">7-й уровень: Телекинетический толчок (Сбить с ног)</content>
|
||||
<content contentuid="h9fa30afdgf4e2g7e74ga57fgdbedd4b1e363" version="1">Когда вы наносите урон цели своим Пси-ударом, вы можете заставить цель совершить спасбросок Силы (Сл 8 + модификатор Интеллекта + бонус мастерства). При провале вы можете наложить на цель состояние «Сбит с ног» или переместить её горизонтально на расстояние до 10 футов.</content>
|
||||
<content contentuid="heae343e1g10f2g46fcgb78cgffc541e0c708" version="1">7-й уровень: Телекинетический толчок (Оттолкнуть)</content>
|
||||
@@ -2407,10 +2407,219 @@
|
||||
<content contentuid="h29815b88g3422g56c3g5aa5gb7806dbe47bc" version="1">Когда существо в пределах 60 футов наносит вам урон, вы можете реакцией нанести ему 1к8 урона громом.</content>
|
||||
<content contentuid="h955ac611g781fg6ebfge3d7gd118e6f415a5" version="1">Большая форма</content>
|
||||
<content contentuid="hb1577082gdd8ag5ff6g60bcgd99dc936bbc9" version="1">Начиная с 5-го уровня персонажа, вы можете бонусным действием увеличить свой размер до большого, если вокруг достаточно места. Это превращение длится 10 минут.</content>
|
||||
<content contentuid="h5d10607dg8858g0d58gde8bgfe26a1630521" version="1">Уровень 3: Вспышка огня заклинаний</content>
|
||||
<content contentuid="h8c1cf02dg530eg20c8ge29dgf4ccedf5993c" version="1">Когда в свой ход вы тратите как минимум 1 Очко Чародейства в рамках Магического действия или Бонусного действия, вы можете высвободить один из следующих магических эффектов по своему выбору. Вы можете сделать это только один раз за ход.<br><br>Укрепляющее пламя. Вы или одно видимое вами существо в пределах 30 футов от себя получаете Временные очки здоровья, равные 1к4 плюс ваш модификатор Харизмы.<br><br>Сияющий огонь. Одно видимое вами существо в пределах 30 футов от себя получает 1к4 урона огнём или излучением (на ваш выбор).</content>
|
||||
<content contentuid="h792eabcbgff7bgb40dg8713g68bba5de6f04" version="1">Уровень 3: Заклинания огня заклинаний</content>
|
||||
<content contentuid="h848282acg476egf71fgd7feg17ed2acf814f" version="1">При достижении определённых уровней чародея у вас всегда будут подготовлены определённые заклинания. На 3-м уровне у вас всегда подготовлены «Лечение ран», «Направляющий луч», «Малое восстановление» и «Опаляющий луч». На 5-м уровне у вас также подготовлены «Аура жизненной силы» и «Контрзаклинание». На 7-м уровне у вас также подготовлены «Огненный щит» и «Стена огня». На 9-м уровне у вас также подготовлены «Высшее восстановление» и «Огненный столп»</content>
|
||||
<content contentuid="h1381ca97g4947g98bdg940ag252c0d161fbf" version="1">Уровень 6: Поглощение заклинаний</content>
|
||||
<content contentuid="hf28a6857g5521g1f6eg1195g302e7c01246f" version="1">Каждый раз, когда цель проваливает спасбросок против сотворённого вами «Контрзаклинания», вы восстанавливаете 1к4 Очков Чародейства.</content>
|
||||
<content contentuid="h602c4560gbaadg43f4gca20g38ba13dd40b6" version="1">Вспышка огня заклинаний</content>
|
||||
<content contentuid="h34d5fc08g03dage117ge30cge3bac9365062" version="1">Когда в свой ход вы тратите как минимум 1 Очко Чародейства в рамках Магического действия или Бонусного действия, вы можете высвободить один из следующих магических эффектов по своему выбору. Вы можете сделать это только один раз за ход.<br><br>Укрепляющее пламя. Вы или одно видимое вами существо в пределах 30 футов от себя получаете Временные очки здоровья, равные 1к4 плюс ваш модификатор Харизмы.<br><br>Сияющий огонь. Одно видимое вами существо в пределах 30 футов от себя получает 1к4 урона огнём или излучением (на ваш выбор).</content>
|
||||
<content contentuid="ha9f42619g46e1gf33agd7f6gca23d5d634b0" version="1">Вспышка огня заклинаний</content>
|
||||
<content contentuid="h8a4d1ebag9e23g557bg322agfe82662adcaa" version="1">Когда в свой ход вы тратите как минимум 1 Очко Чародейства в рамках Магического действия или Бонусного действия, вы можете высвободить один из следующих магических эффектов по своему выбору. Вы можете сделать это только один раз за ход.</content>
|
||||
<content contentuid="h33522f06g6cadgaee9g398eg0bd52adb964b" version="1">Укрепляющее пламя</content>
|
||||
<content contentuid="h0ffef888g07e1g2e7dgf090g1e02c22995ae" version="1">Вы или одно видимое вами существо в пределах 30 футов от себя получаете Временные очки здоровья, равные 1к4 плюс ваш модификатор Харизмы.</content>
|
||||
<content contentuid="hcbc34c94g87dagca44ge6bcg1c07a081e603" version="1">Сияющий огонь: Огонь</content>
|
||||
<content contentuid="h37be3b80g09fbg170eg2b7cg065997dc9bf9" version="1">Одно видимое вами существо в пределах 30 футов от себя получает 1к4 урона огнём.</content>
|
||||
<content contentuid="h14663f97g9c94ge2fegb1d6g11e9d19efb32" version="1">Сияющий огонь: Излучение</content>
|
||||
<content contentuid="hf4271cc8ge175g82fcg26deg011060d3570a" version="1">Одно видимое вами существо в пределах 30 футов от себя получает 1к4 урона излучением.</content>
|
||||
<content contentuid="h9de61a36g2e2dgb757g472cge372a5d76c6d" version="1">Укрепляющее пламя</content>
|
||||
<content contentuid="h5a2f62c0g20c4g7473ge5fagb7ae81549ea5" version="1">Получите Временные очки здоровья, равные 1к4 плюс модификатор Харизмы заклинателя.</content>
|
||||
<content contentuid="hebae72e1ga142gbc3dg8f84ge0a92b2e9d8e" version="1">Чародейство огня заклинаний</content>
|
||||
<content contentuid="h60db9c86g6587g9b47g7ddcgd3131d7fdb99" version="1">Ваша врождённая сила исходит из самого источника магии: Плетения. Эта связь проявляется как редкая способность, известная как огонь заклинаний, и вы переполняетесь сияющими всплесками этой необузданной магии. Ваш дар огня заклинаний позволяет вам исцелять союзников, выжигать врагов и поглощать мощные заклинания.<br><br>Носителей огня заклинаний часто тянет к странствиям. Многие путешествуют между крупными поселениями, например вдоль Побережья Мечей, и используют свою магию на благо простых людей. Другие постигают собственные необычные силы, бродя по столь же необычным землям, от выжженных магией пустошей Анораха до коснувшихся богов диких краёв Древних Империй. Куда бы они ни отправились в Королевствах, чародеи огня заклинаний неизменно привлекают внимание организаций, заинтересованных в тайных искусствах, таких как Арфисты, Культ Дракона и Красные Волшебники.</content>
|
||||
<content contentuid="hb1ace68ega6a4gd1e5g3bb4g470b754bbd3b" version="1">Стиснуть зубы</content>
|
||||
<content contentuid="h5c32e99cgde90g115cgb925g8d1863515d98" version="2">Вы можете потратить одну Кость риска, чтобы получить Временные очки здоровья, равные выпавшему результату плюс ваш уровень Стрелка.</content>
|
||||
<content contentuid="h954e64afgfcbegba5ag9418g03aeba64afba" version="1">Стрельба вслепую</content>
|
||||
<content contentuid="h133443e3g83d1g23b9gaa79g6afa4fd2cc92" version="2">Вы можете потратить одну Кость риска, чтобы получить Слепое зрение в радиусе 30 футов до конца текущего хода.</content>
|
||||
<content contentuid="h6aee83f0gafd8gb807g3a1bg78fbd25f3f9b" version="1">Перекат</content>
|
||||
<content contentuid="h811be34fge8e2ga406g6b57gb6eda91fa978" version="2">Вы можете потратить одну Кость риска, чтобы переместиться на расстояние до 30 футов. Это перемещение не провоцирует атак по возможности и не зависит от труднопроходимой местности.</content>
|
||||
<content contentuid="h251764bbg0827g7973g1fa1g06a686073a3e" version="1">Стиснуть зубы</content>
|
||||
<content contentuid="h1e6725ecg25f9g4965g6e7dg3fb50e04d1db" version="2">Получите Временные очки здоровья, равные выпавшему результату на Кости риска плюс ваш уровень Стрелка.</content>
|
||||
<content contentuid="h86d52560g0e7fga4d6g37b2gbf26b4ebf7bc" version="1">Стрельба вслепую</content>
|
||||
<content contentuid="h3f23c59cg5062g7897g047dg8aa2c35442a2" version="1">Вы можете Бонусным действием потратить одну Кость риска, чтобы получить Слепое зрение в радиусе 30 футов до конца текущего хода.</content>
|
||||
<content contentuid="h0c28325bgfcc5ga21cgf52eg7ebe1e5e3de6" version="1">Кости риска</content>
|
||||
<content contentuid="ha02a83edg0b59g9fadg6c2ag3f1117de4840" version="1">Вы способны на невероятные дерзкие трюки, подпитываемые особыми костями, называемыми Костями риска.</content>
|
||||
<content contentuid="h181a6b08gd85dg6c1eg30edgfaac60114965" version="1">Скользящий выстрел</content>
|
||||
<content contentuid="h179791c5gff57g15ddg6254g9297c342cf48" version="2">Когда вы промахиваетесь по существу броском атаки дальнобойным оружием, вы можете потратить одну Кость риска (без действия), чтобы нанести этому существу урон, равный результату броска кости плюс ваш модификатор Ловкости (минимум 1). Этот урон имеет тот же тип, что и урон оружия, и может увеличиваться только за счёт увеличения модификатора характеристики. Вы можете использовать этот манёвр только один раз за ход.</content>
|
||||
<content contentuid="hfa0d0b7bg176fg70c4g5fd6gdcbb10cd700b" version="1">Дух сорвиголовы</content>
|
||||
<content contentuid="h7ac87358g45e1g10c6ga7c3g85b624d04c91" version="2">Когда вы проваливаете проверку характеристики или спасбросок Интеллекта, Мудрости или Харизмы, вы можете потратить одну Кость риска и добавить её к броску, потенциально превращая его в успех. Вы можете использовать этот манёвр только один раз за ход.</content>
|
||||
<content contentuid="h6da5659ag3c1cg9cb9g8b92g4c66945a42fc" version="1">На волосок от беды</content>
|
||||
<content contentuid="h2efd301eg6691g78bfg2023g744d09c12743" version="2">Когда видимое вами существо попадает по вам броском атаки, вы можете Реакцией потратить одну Кость риска, чтобы уклониться от удара. Бросьте кость и добавьте выпавшее значение к вашему КД против этой атаки, потенциально заставив её промахнуться. Вы можете использовать этот манёвр только один раз за ход.</content>
|
||||
<content contentuid="h92d115e1gde3cg3ce4g07ebg1a8454c06194" version="1">Уровень 10: Любитель риска</content>
|
||||
<content contentuid="ha818ea6cg6ab5g343dga9bag8a6101e2e6ac" version="1">Уровень 3: Непроницаемое лицо</content>
|
||||
<content contentuid="h9fff4318g1d24gfe2bgf7a2g1e0bcaa80c6d" version="1">Вы получаете владение всеми игровыми наборами и одним из следующих навыков на ваш выбор: Обман, Проницательность или Внимательность.</content>
|
||||
<content contentuid="hf4b9bf75gff89ga519g3c73g4e032d899801" version="1">Уровень 3: Кости лжеца</content>
|
||||
<content contentuid="h0602a34cg6491g63b2g1d28g66f745219e94" version="1">Уровень 6: Рискованное дело</content>
|
||||
<content contentuid="hb4e272e9gcca8g7303g07c8g417f217bf042" version="1">Вы можете использовать манёвры «Дух сорвиголовы» и «На волосок от беды» без траты Кости риска. В этом случае вы бросаете к6 вместо Кости риска.</content>
|
||||
<content contentuid="h86505f38g7a8bgd372ge5ebg631424f992ff" version="2">Потратив одну Кость риска, вы бросаете к20. Если результат равен 10 или выше, урон вашей следующей атаки дальнобойным оружием удваивается. Если результат ниже 10, урон ваших атак дальнобойным оружием в этом ходу уменьшается вдвое.</content>
|
||||
<content contentuid="h50a60375g5ff1gecd3g534eg1d38083f84f2" version="1">Один раз за ход, когда вы совершаете бросок атаки по врагу и этот бросок не совершается с помехой, вы можете выбрать совершить его с помехой. Если вы это делаете, то восстанавливаете одну потраченную Кость риска.</content>
|
||||
<content contentuid="h43421e50g805fg598bg75d5g05e5b202e34a" version="1">Рискованное дело</content>
|
||||
<content contentuid="h8416536fg8e84gf01eg11c4g49188075bb83" version="1">Кости лжеца</content>
|
||||
<content contentuid="h463652f9g2026g8164g2cf3g5dcaab1b644f" version="2">Потратив одну Кость риска, вы бросаете к20. Если результат равен 10 или выше, урон вашей следующей атаки дальнобойным оружием удваивается. Если результат ниже 10, урон ваших атак дальнобойным оружием в этом ходу уменьшается вдвое.</content>
|
||||
<content contentuid="hb4ca4223gcc87g7a48g70f8ge8b72f98226f" version="1">Джекпот</content>
|
||||
<content contentuid="h42c83e2fgb526ge712gd8fbg5dd17226f6f9" version="1">Проигрыш</content>
|
||||
<content contentuid="hedc8f50dg2cfege304g3e25gaf93f9dfc2e9" version="1">Урон вашей следующей атаки дальнобойным оружием удваивается.</content>
|
||||
<content contentuid="h396d95d1gb027gae12ge9f1gfd524d0a2828" version="1">Урон ваших атак дальнобойным оружием в этом ходу уменьшается вдвое.</content>
|
||||
<content contentuid="h852be075ged2egce53g06ddg5c3bd7f0ea3c" version="1">Рискованное дело</content>
|
||||
<content contentuid="h085e0a00g7d78gfc3dgbd2bg6ac83183e80e" version="1">Один раз за ход, когда вы совершаете бросок атаки по врагу и этот бросок не совершается с помехой, вы можете выбрать совершить его с помехой. Если вы это делаете, то восстанавливаете одну потраченную Кость риска.</content>
|
||||
<content contentuid="hb415e825g6438g17d3g2957g78ef9a062b18" version="1">На волосок от беды (Любитель риска)</content>
|
||||
<content contentuid="h498a6304ge277g7fbcg5ab2g9164df541234" version="1">Дух сорвиголовы (Любитель риска)</content>
|
||||
<content contentuid="he1d26149g3d4dgbec3g0b58g70c8afd98072" version="1">Крупный игрок</content>
|
||||
<content contentuid="h1594d7ffge703g7b01g1f9bg2776670f8c1f" version="1">Удача капризна, если только вы не крупный игрок. Эти Стрелки виртуозно обращаются с картами и костями, сочетая любовь к риску с талантом к стрельбе. Крупные игроки испытывают удачу, пока она не отвернётся, а затем давят ещё сильнее. Зачем довольствоваться обычной победой, если можно поставить всё и сорвать большой куш?</content>
|
||||
<content contentuid="hd5cd42c6g00b9gf97bg188bgb80ad6f17e94" version="1">Заклинострел</content>
|
||||
<content contentuid="hc1e20b2eg75ccg05fcgecaag065752a6e0ac" version="1">Магия и огнестрел не так уж различны: тайная сила подобна пороху, заклинание похоже на пулю, а вы сами словно оружие, с точностью направляющее чары в несчастные цели. Заклинострелы смешивают искусство стрельбы и магии, порой заряжая выстрелы тайной энергией и выпуская пули, усиленные молнией, холодом или пламенем.</content>
|
||||
<content contentuid="hcf49c14dg93ceg7906g770dg50e2ea6a3c87" version="1">Белая шляпа</content>
|
||||
<content contentuid="h5e8ae88cg3e3fg20acg354bg8c4bea47525e" version="1">Некоторые Стрелки живут по кодексу и ожидают того же от других. Таких Стрелков, известных как Белые шляпы, иногда можно встретить на службе закона, но они никогда не колеблются поступить правильно, если закон велит иначе. Несмотря на привязанность к смертоносному оружию, Белые шляпы предпочитают защищать друзей и усмирять врагов без лишней крови, хотя сами враги редко готовы им это позволить.</content>
|
||||
<content contentuid="h39141f86g1ee2ga21ag8d53ga05af28fd1f7" version="3">Пистолет</content>
|
||||
<content contentuid="hf305122ag44adg88bag55cbgd8db4c039656" version="3">Самый ранний тип одноручного огнестрельного оружия. Пистолет действует как уменьшенный мушкет, заряжая и выпуская один тяжёлый снаряд с смертоносным результатом.</content>
|
||||
<content contentuid="h1060906fg1496g1454g9846g4f2197c2c188" version="1">Огнестрельное оружие</content>
|
||||
<content contentuid="h05daeb7eg0489g5774g293ag23a57911c159" version="1">Если у вас нет особенности «Эксперт по огнестрельному оружию», вы не добавляете модификатор характеристики к броскам урона этим оружием.</content>
|
||||
<content contentuid="h7ded197eg2127g6036ge3dcgb2d60fbb65bf" version="1">Огнестрельное оружие</content>
|
||||
<content contentuid="hafe832f4g5639gf264g68a3ge1ab2fdd021c" version="1">Если у вас нет особенности «Эксперт по огнестрельному оружию», вы не добавляете модификатор характеристики к броскам урона этим оружием.</content>
|
||||
<content contentuid="he70e8fadg370cg0fbbgffa4gbdf8dc36cb12" version="1">Эксперт по огнестрельному оружию</content>
|
||||
<content contentuid="h99e9e197g55f2g72f5g3b37g75a3f15b60bc" version="1">Вы эксперт по огнестрельному оружию. Вы можете добавлять модификатор характеристики к броскам урона атак оружием, совершённых огнестрельным оружием.</content>
|
||||
<content contentuid="h4e6ec503g245cg8614g1c81g7087625fa70e" version="1">Эксперт по огнестрельному оружию</content>
|
||||
<content contentuid="h86b389a9gfe27g7353g9f20g12d9431a57fa" version="1">Вы эксперт по огнестрельному оружию. Вы можете добавлять модификатор характеристики к броскам урона атак оружием, совершённых огнестрельным оружием.</content>
|
||||
<content contentuid="h61b78046g443bg1d02ge5bbgfe44da9ef621" version="4">Мушкет</content>
|
||||
<content contentuid="h4b2511a0g4888gc3ffg4e1fgc9d9ee28927a" version="4">Длинноствольное огнестрельное оружие, рассчитанное на использование двумя руками. Мушкет наносит мощные и точные выстрелы, выпуская на большие расстояния один тяжёлый снаряд.</content>
|
||||
<content contentuid="hae35f153g7a35g4565ga666g79bb16da7dd3" version="1">Драконья форма</content>
|
||||
<content contentuid="hb5de1336geb7cg937fg5ba9g13226b34f6df" version="1">Оружие дыхания</content>
|
||||
<content contentuid="h82f57f8fg4253ga79egce21gde7d5fe72f73" version="2">Вы можете действием выдохнуть изнутри себя поток могучей энергии. Каждое существо в конусе длиной 15 футов должно совершить спасбросок Ловкости против вашей Сл заклинаний. При провале существо получает урон огнём, а при успехе - половину этого урона.</content>
|
||||
<content contentuid="h6d46b975ga195gac49gab4cg2ff48ea0ff54" version="1">На определённых уровнях ваше оружие дыхания становится сильнее. На 6-м уровне оно наносит 3к6 урона. На 10-м уровне - 4к6.</content>
|
||||
<content contentuid="ha7a4dc42gd5c2g913bg0030gb9bacfc6f4d8" version="1">Драконья форма</content>
|
||||
<content contentuid="hea98e86cg6f0eg6fa9gd366g94bd7a432ec2" version="2">Вы можете потратить одно использование особенности «Дикий облик» Бонусным действием, чтобы принять уникальную форму: вашу драконью форму. В этой форме вы становитесь драконом среднего размера, стоящим на четырёх лапах, но сохраняете свои обычные характеристики и чувства.<br><br>Находясь в Драконьей форме, вы можете использовать атаки когтями и оружие дыхания. Вы получаете скорость полёта, равную удвоенной скорости передвижения, а также Сопротивление урону кислотой, холодом, огнём, электричеством и ядом.</content>
|
||||
<content contentuid="hc56adb28g1c39g4a79g84bbgc0a1b6238a22" version="1">Когда вы достигаете 6-го и 10-го уровней друида, ваша Драконья форма становится сильнее.</content>
|
||||
<content contentuid="h8a2a463cg9e0fg4e84gefd3g2307577a9b84" version="1">Круг драконов</content>
|
||||
<content contentuid="h4dcc12fag7d0cgb63ag23cfg441ee129b6df" version="1">Круг драконов - древний орден друидов, глубоко погружённый в суровые традиции. Эти связанные честью хранители природы и драконьего наследия состоят в тайном обществе, которое влияло на власть, войны и культуру по всему миру. Высокопоставленные члены этого Круга связаны с королевскими родами, чьи линии уходят в далёкое прошлое, и эта связь едва заметно отражена в гербах и знаках царствующих семей.<br><br>Друиды этого Круга знают, что драконы и драконья магия связаны с миром не меньше, чем растения и звери, и используют эту связь, чтобы превращаться в собственную уникальную и могучую драконью форму.</content>
|
||||
<content contentuid="hea0350ecg3563gf151g3949ge2a04d0eb3da" version="1">Уровень 3: Драконьи знания</content>
|
||||
<content contentuid="hda6df4cdg93a3gab23g3fd8ga7f8a4585d19" version="1">Вы получаете экспертность в проверках Истории.</content>
|
||||
<content contentuid="h8ea85d5eg5bc3gd5aagf3e6g7179f506b3ef" version="1">Уровень 3: Драконья форма</content>
|
||||
<content contentuid="hc9360440gfe54g46f9gbb9dge1b352d710ba" version="2">Вы можете потратить одно использование «Дикого облика» Бонусным действием, чтобы принять уникальную форму: вашу Драконью форму. В этой форме вы становитесь драконом среднего размера, стоящим на четырёх лапах, но сохраняете свои обычные игровые характеристики и чувства.<br><br>При принятии Драконьей формы вы получаете Временные очки здоровья, равные утроенному уровню друида. Находясь в Драконьей форме, вы можете использовать атаки когтями и оружие дыхания. Вы получаете скорость полёта, равную удвоенной Скорости, а также Сопротивление урону кислотой, холодом, огнём, электричеством и ядом.</content>
|
||||
<content contentuid="hcb4664e4g5158g21d8ga6dcgb912ed163b0d" version="1">Когда вы достигаете 6-го и 10-го уровней друида, ваша Драконья форма становится сильнее.</content>
|
||||
<content contentuid="h1dfb4f40g1c13gaa7fg1d57g9a9c0d160f6b" version="1">Уровень 6: Улучшенная Драконья форма</content>
|
||||
<content contentuid="h188fcbccg032fg827cg4fc8g0526eb475df0" version="1">Находясь в Драконьей форме, ваши атаки считаются магическими для преодоления <LSTag Tooltip="Resistant">Сопротивления</LSTag> и <LSTag Tooltip="Immune">Иммунитета</LSTag> к немагическому урону, а ваш КД увеличивается на 1.</content>
|
||||
<content contentuid="hd3ad95cbg5d22g8cf8g0922gb17a3e44311f" version="1">Уровень 10: Драконья магия</content>
|
||||
<content contentuid="h8a430f87g813dg7f42g06f9gd34f0e6f79f3" version="1">Находясь в Драконьей форме, вы можете накладывать подготовленные заклинания друида, а ваш КД увеличивается на 2.</content>
|
||||
<content contentuid="h7ee8c4acge213gdc57g0743g89e268308429" version="1">Нажмите на значок ячейки заклинаний на панели быстрого доступа, чтобы использовать подготовленные заклинания.</content>
|
||||
<content contentuid="he87fc2b8g89e0g6349gbba8g169827090bfc" version="1">Дорожный налётчик</content>
|
||||
<content contentuid="h71ff1a29gf725g3e28gb5bfg5096950ebc3f" version="1">Подстерегая путников на просёлках, Дорожный налётчик вселяет страх в сердце каждого путешественника и прижимистого торговца. Он настигнет добычу верхом на быстром и верном скакуне, а затем мгновенно скроется.</content>
|
||||
<content contentuid="hf94de242ge211g147egfef6gf0c946d2ffe0" version="1">Уровень 3: Быстрая выхватка</content>
|
||||
<content contentuid="h83b92101gfc2bg31e9gf707gf2e81d4a43fc" version="3">Вы получаете экспертность с огнестрельным оружием. Кроме того, вы можете совершить одну атаку оружием в первый ход боя без затраты действия.</content>
|
||||
<content contentuid="hcc27b910g6850g1264gef1agb05f75743b92" version="1">Уровень 3: Верный скакун</content>
|
||||
<content contentuid="h4bf647c4g9622g1b59gd3ccg7614d1ac3969" version="1">У вас всегда подготовлено заклинание «Обретение скакуна». Благодаря этой особенности вы можете накладывать его без траты ячейки заклинаний и компонентов, а вашей характеристикой сотворения для него является Интеллект.<br><br>После сотворения заклинания таким образом вы не можете сделать это снова, пока не завершите Короткий отдых.</content>
|
||||
<content contentuid="h97981f9bgce50gd7e8g2a87gceb5d667aef5" version="1">Уровень 3: Сбить их с ног</content>
|
||||
<content contentuid="hdd4cfe56g5d63g5f04ge52dg1620a9b457fa" version="1">Находясь верхом на своём скакуне, вам не нужно иметь Преимущество на бросок атаки, чтобы использовать Скрытую атаку, при условии что у вас нет Помехи на этот бросок.</content>
|
||||
<content contentuid="hcf021de0g26a4ge1e4ge868g80ee6afb7879" version="1">Уровень 9: Повелитель коней</content>
|
||||
<content contentuid="h0221fc2dgce10g295eg2d4agd7d6c48ba873" version="1">Когда вы не в бою, вы можете потратить 1 минуту на уход и заботу о своём скакуне, по окончании чего он получает Временные очки здоровья, равные удвоенному уровню вашего плута.</content>
|
||||
<content contentuid="h2881789ag8c50g0643gc4afgdefe86565759" version="1">Повелитель коней</content>
|
||||
<content contentuid="h169d7ad1g382fg6e64gd7bag6b40733be6b3" version="1">Вы можете потратить 1 минуту на уход и заботу о своём скакуне, по окончании чего он получает Временные очки здоровья, равные удвоенному уровню вашего плута.</content>
|
||||
<content contentuid="h7b19797cg51e0ge287ge71eg179578a32576" version="1">Повелитель коней</content>
|
||||
<content contentuid="hc426f330gcc3fge554g4368gf9e59e055631" version="1">Вы можете потратить 1 минуту на уход и заботу о своём скакуне, по окончании чего он получает Временные очки здоровья, равные удвоенному уровню вашего плута.</content>
|
||||
<content contentuid="hfdbc8b12gdf41gcf0fg7579g6557c7859eb6" version="1">Быстрая выхватка</content>
|
||||
<content contentuid="h1b35c761g20a0gdbf9gd311g6f35d870fb66" version="1">Вы можете совершить одну атаку оружием в первый ход боя без затраты действия.</content>
|
||||
<content contentuid="he20aedbcg8a55ge2e1gc6e1g892e80b9dd06" version="1">Быстрая выхватка</content>
|
||||
<content contentuid="h52e0bfc4g2561g410ag637eg759d80be5ce4" version="1">Вы можете совершить одну атаку оружием в первый ход боя без затраты действия.</content>
|
||||
<content contentuid="h613fc304gdb88g758bgc98cg5ab761c0998b" version="1">Быстрая выхватка</content>
|
||||
<content contentuid="hc50e8dbcg2bbagfb26gb64fg9efcb00f26ed" version="1">Вы можете совершить одну атаку оружием в первый ход боя без затраты действия.</content>
|
||||
<content contentuid="h06dee418g7023gcae2g1dd4g7246a7a21faf" version="1">Руна огня</content>
|
||||
<content contentuid="h0738e060g6dbdgd884ga7eag9103f532652e" version="1">Пока цель скована оковами, в начале каждого своего хода она получает 2к6 урона огнём. В конце каждого своего хода цель может повторять спасбросок, при успехе рассеивая оковы.</content>
|
||||
<content contentuid="h07ffaea2g95cdgb266g0772gc36a0178e777" version="1">Уровень 3: Рождённый в седле</content>
|
||||
<content contentuid="h0e929a83g6124gede3gd6d6g810f31fc91bf" version="1">Рунические рыцари усиливают своё боевое мастерство сверхъестественной силой рун - древней практикой, берущей начало у великанов. Резчики рун встречаются среди любых родов великанов, и, вероятно, вы переняли свои методы из первых или вторых рук у такого мистического мастера. Найдя ли вы работу великанов, высеченную в холме или пещере, узнали ли о рунах от мудреца или встретили великана лично, вы изучили ремесло великанов и научились применять магические руны для усиления своего снаряжения.</content>
|
||||
<content contentuid="h19db7115g1409g1483g8306g3b0909c3bde9" version="1">Кавалер</content>
|
||||
<content contentuid="h1c85d7b8gf5dcge550gda8dgd5776880afb3" version="1">Пророчество шторма: Преимущество</content>
|
||||
<content contentuid="h234cd41egb166g8eb3g6939g36498884c3ea" version="1">Уровень 3: Мощь великана</content>
|
||||
<content contentuid="h2c76a4f7g48d1g3368ga27agbd1576843d8d" version="1">Уровень 7: Рунический щит</content>
|
||||
<content contentuid="h37b450b1g8badgbc5bg97c2g324b484cc3e8" version="1">Рунический щит</content>
|
||||
<content contentuid="h39e677d5ged8fgc431g5486g61bd08885fd5" version="1">У вас всегда подготовлено заклинание «Метка охотника». Вы можете накладывать его число раз, равное вашему модификатору Силы (минимум один раз), и восстанавливаете все израсходованные использования после завершения продолжительного отдыха.</content>
|
||||
<content contentuid="h3a4d5e28gabe3g3168ge119g17a00c4aa941" version="1">Призыв руны огня</content>
|
||||
<content contentuid="h3d68e61ag18degff1egcb3cg93abd525a72f" version="1">Руна шторма</content>
|
||||
<content contentuid="h4a3c73bbg633cg224dg4073g6346f0550299" version="1">Исполинский рост</content>
|
||||
<content contentuid="h4e152278g0424g0354g3000g79c86331ed68" version="1">Уровень 10: Исполинский рост</content>
|
||||
<content contentuid="h51fe1e07g2424g5ddcg0df6g20465b6f7342" version="1">Вы получаете владение одним навыком на выбор: Дрессировка, История, Проницательность, Выступление или Убеждение.</content>
|
||||
<content contentuid="h5873afb5ga1a1gb7ddgaea4gaf5ab082ffd4" version="1">Вы можете призвать руну бонусным действием, получая на 1 минуту сопротивление дробящему, колющему и рубящему урону.</content>
|
||||
<content contentuid="h59e2bd3dg4c8fgb3e9g4be1g17c0354f71d2" version="1">Руна</content>
|
||||
<content contentuid="h61c977efg9e94ga7feg7173g45a085a426e1" version="1">Получите бонус +2 ко всем проверкам характеристик и спасброскам, использующим Силу или Телосложение.</content>
|
||||
<content contentuid="h62929300g0a1ag65dcg0311gee7ce76b7030" version="1">Рунический рыцарь</content>
|
||||
<content contentuid="h63ce4017geac0gc60egf8b5g9dce552352fa" version="1">Уровень 3: Дополнительное владение</content>
|
||||
<content contentuid="h6d3bae27g9034g8149g3867g484287a79d30" version="1">Вы становитесь мастером сдерживания врагов. Существа провоцируют атаку по возможности с вашей стороны, когда перемещаются на 5 футов или более, оставаясь в пределах вашей досягаемости, и если вы попадаете такой атакой по возможности, скорость цели снижается до 0 до конца текущего хода.</content>
|
||||
<content contentuid="h70f68e2egfe60g8371gc262g13645f286d18" version="1">Вы можете призвать руну бонусным действием, чтобы войти в пророческое состояние на 1 минуту или до тех пор, пока не станете недееспособны. Пока состояние активно, когда вы или другое существо в пределах 60 футов, которое вы видите, совершает бросок атаки, спасбросок или проверку характеристики, вы можете использовать реакцию, чтобы дать этому броску преимущество или помеху.</content>
|
||||
<content contentuid="h75559935g2e90ga5d1g57b2g1393773adde2" version="3">Вы можете призвать руну бонусным действием, чтобы войти в пророческое состояние на 1 минуту или до тех пор, пока не станете недееспособны. Пока состояние активно, когда вы или другое существо в пределах 60 футов, которое вы видите, совершает бросок атаки, спасбросок или проверку характеристики, вы можете использовать реакцию, чтобы дать этому броску преимущество или помеху.</content>
|
||||
<content contentuid="h7ac02ad8g0798gdcabg9abag78fd5b57ba92" version="1">Кавалер преуспевает в конном бою. Обычно рождённый среди знати и воспитанный при дворе, кавалер одинаково уверенно ведёт кавалерийскую атаку и поддерживает светскую беседу на государственном приёме. Кавалеры также учатся оберегать тех, кто находится под их защитой, и часто служат защитниками своих командиров и слабых. Стремясь исправлять несправедливость или заслужить славу, многие из этих воинов оставляют комфортную жизнь ради великих приключений.</content>
|
||||
<content contentuid="h7d3eb606ga301ge0f4g8d8ag0eb706dd27d1" version="1">Защитный манёвр</content>
|
||||
<content contentuid="h80302dc6gefa4ga829g206ag2011c0e0baca" version="2">Когда вы попадаете по существу атакой оружием, вы можете призвать руну, чтобы создать огненные оковы: цель получает дополнительные 2к6 урона огнём и становится скованной на 1 минуту. Пока цель скована оковами, в начале каждого своего хода она получает 2к6 урона огнём. В конце каждого своего хода цель может повторять спасбросок, при успехе рассеивая оковы.</content>
|
||||
<content contentuid="h80b3909egdd5dg9771g4878g32dff4066380" version="1">Когда вы или другое существо в пределах 60 футов, которое вы видите, совершает бросок атаки, спасбросок или проверку характеристики, вы можете использовать реакцию, чтобы дать этому броску преимущество.</content>
|
||||
<content contentuid="h87466615g2070gcbacg5d8ag5f7ab7b958fe" version="1">Мощь великана</content>
|
||||
<content contentuid="h8810d94cg3f6ag7eebgb3e6g1e498faee90a" version="1">Уровень 3: Резчик рун</content>
|
||||
<content contentuid="h8eaac1bdg758fg66f4g7051gef7c1044f979" version="1">Вы получаете бонус +2 ко всем проверкам характеристик и спасброскам, использующим Силу или Телосложение.</content>
|
||||
<content contentuid="h8eb71fdcge67eg9086g4432gf36ff5890bba" version="1">Пророчество шторма: Помеха</content>
|
||||
<content contentuid="h92ac32f2gae16g6effgdffege7984a067a57" version="1">У вас всегда подготовлено заклинание «Обретение скакуна». Благодаря этой особенности вы можете накладывать его без траты ячейки заклинаний и компонентов, а вашей характеристикой сотворения для него является Интеллект.<br><br>После сотворения заклинания таким образом вы не можете сделать это снова, пока не завершите короткий отдых.</content>
|
||||
<content contentuid="h96dd1a79g41bfg7310gfaedg7ce04dd1acf3" version="1">Вы становитесь мастером сдерживания врагов. Существа провоцируют атаку по возможности с вашей стороны, когда перемещаются на 5 футов или более, оставаясь в пределах вашей досягаемости, и если вы попадаете такой атакой по возможности, скорость цели снижается до 0 до конца текущего хода.</content>
|
||||
<content contentuid="h99fb6d15gdad4gf9ebgb1f5gfc423b34bdf4" version="1">Руна мороза</content>
|
||||
<content contentuid="h9f5052c1ga6c8ga38cg6399gbc4405cf0442" version="2">Вы учитесь призывать магию рун, чтобы защищать союзников. Когда другое существо в пределах 60 футов, которое вы видите, получает попадание атакой, вы можете использовать реакцию, чтобы заставить атакующего перебросить d20 и использовать новый результат.</content>
|
||||
<content contentuid="ha0cc673dgbc99ga346g948dg56823a07cb44" version="1">Увеличьтесь в размерах, чтобы стать сильнее.</content>
|
||||
<content contentuid="ha2bb03e3g700ag8cdagaff7gd4fdd097b625" version="1">Вы учитесь отражать удары, направленные на вас, вашего скакуна или других существ рядом. Если вы или существо в пределах 5 футов, которое вы видите, получает попадание атакой, вы можете использовать реакцию, чтобы бросить 1к8, если держите оружие ближнего боя или щит. Прибавьте выпавшее значение к КБ цели против этой атаки, что может привести к промаху.</content>
|
||||
<content contentuid="ha4dc5a17g4a69gb755g889cg187ead2dfb7f" version="1">Мощь великана</content>
|
||||
<content contentuid="ha67185a5gec5fg7accg1041g41f8330b1d17" version="1">Пророчество шторма: Помеха</content>
|
||||
<content contentuid="ha906615bge33ag2df4g32dbg148dd65b9aac" version="1">Пророчество шторма: Преимущество</content>
|
||||
<content contentuid="ha9c39a13gb33cg2948g41efg78e3649d79ac" version="1">Призыв каменной руны</content>
|
||||
<content contentuid="hb0de3a5fgafe3g904agae5fgdfc2d5a9d82b" version="1">Уровень 3: Непоколебимая метка</content>
|
||||
<content contentuid="hb3ae9e73g81e6g120dg8548g27684eff651f" version="1">Призыв облачной руны</content>
|
||||
<content contentuid="hbb926017g2b0agbe90g66bcgbf207e483612" version="1">Уровень 7: Защитный манёвр</content>
|
||||
<content contentuid="hbeb30ce1gced1g1c41gd105g57e6ea60c80e" version="1">Вы можете призвать Каменную руну, чтобы ввести существо в гипнотическое состояние на 1 минуту.</content>
|
||||
<content contentuid="hc87e44d6gbe9cg19ceg0681g7bf7a87f2bd0" version="1">Вы учитесь призывать магию рун, чтобы защищать союзников. Когда другое существо в пределах 60 футов, которое вы видите, получает попадание атакой, вы можете использовать реакцию, чтобы заставить атакующего перебросить d20 и использовать новый результат.</content>
|
||||
<content contentuid="hd234d1a5g51feg1581ga07ag43fc0a63f55a" version="1">Уровень 10: Удержание рубежа</content>
|
||||
<content contentuid="hd49600e9gaa19g5b9bgab38g6740603067eb" version="1">Призыв руны мороза</content>
|
||||
<content contentuid="hdbd971c9g92f8ga84bg8f1eg267b38cd8ffc" version="1">Дополнительный урон от вашей особенности «Мощь великана» увеличивается до 1к8.</content>
|
||||
<content contentuid="he7d011e0gaae4g9343gae55g467fb0606a35" version="1">Вы учитесь отражать удары, направленные на вас, вашего скакуна или других существ рядом. Если вы или существо в пределах 5 футов, которое вы видите, получает попадание атакой, вы можете использовать реакцию, чтобы бросить 1к8, если держите оружие ближнего боя или щит. Прибавьте выпавшее значение к КБ цели против этой атаки, что может привести к промаху.</content>
|
||||
<content contentuid="hea634b92g74a3g2000gc22bgfecaeff7c7dd" version="1">Призыв руны шторма</content>
|
||||
<content contentuid="hea790591g5d78g720ag188bgcefd45615ff0" version="1">Призыв руны холма</content>
|
||||
<content contentuid="hea7ba403g54b1gb4c6g477cg081231fd6e91" version="2">Вы можете использовать магические руны, чтобы усилить своё снаряжение. На 3-м уровне вы изучаете руны Облака, Огня, Мороза и Камня. На 7-м уровне вы изучаете руны Холма и Шторма.<br><br>Вы можете использовать руны число раз в зависимости от уровня воина: 3 использования на 3-м уровне, 4 использования на 7-м уровне и 5 использований на 10-м уровне. Вы восстанавливаете все израсходованные использования после короткого отдыха.<br><br>Если руна требует спасбросок, его СЛ равна 8 + ваш бонус мастерства + ваш модификатор Телосложения.</content>
|
||||
<content contentuid="hf11839f6g011fg9755gabadga421a00c9ed3" version="1">Удержание рубежа</content>
|
||||
<content contentuid="hf2959cc8g6d39ge50dg36c2gf91080392da0" version="1">Когда вы или другое существо в пределах 60 футов, которое вы видите, совершает бросок атаки, спасбросок или проверку характеристики, вы можете использовать реакцию, чтобы дать этому броску помеху.</content>
|
||||
<content contentuid="hfd1bcc8cg898egba47gb579gfd786513a783" version="1"><LSTag Type="Status" Tooltip="CHARMED">Очаруйте</LSTag> врага, который атакует вас. По возможности он атакует новую цель.</content>
|
||||
<content contentuid="hffd4abcage782g546dgaeacgc8fe055bd42e" version="2">Бонусным действием вы получаете силу великанов на 1 минуту. Вы становитесь Большим, получаете преимущество на проверки Силы и спасброски Силы, а ваши атаки оружием или безоружные атаки наносят при попадании дополнительный урон 1к6.</content>
|
||||
<content contentuid="hffe0a439g2edag0f4bg85dbg933db0b1a8b0" version="1">Рунический щит</content>
|
||||
<content contentuid="h4d43ff27g7af4g7c74gd48dg5f5bf9706659" version="1">1</content>
|
||||
<content contentuid="h1479e906gc7dag1d98gf6d0g21845e6cae8e" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h23312037g140dg2713g1d14gd9b396f2c1e0" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h2d9c047cg3532g702ege224g72b70a01e316" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h2fa825ceg9a28g20c8g48d0g40216361a33e" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h30b03151g5293g0f58g25ecg192e954a1d18" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h368dfb6eg54d1g9117gae02g8877cb540a10" version="1">Уровень 6: Чародейский стрелок</content>
|
||||
<content contentuid="h3b7d345ag5345g49ffg6c00g90a138a19f95" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="h3d2a0062g8a04g4942g776fg8389d4b68f1b" version="1">Примите облик белого медведя, который может <LSTag Type="Spell" Tooltip="Shout_GoadingRoar_Bear_Summon">Провоцировать</LSTag> врагов атаковать его.</content>
|
||||
<content contentuid="h4afbde24g3b4ag6245gdfc5gf40fbb6dd7b9" version="1">Уровень 3: Бах! И ты труп!</content>
|
||||
<content contentuid="h4b3edb5dg700eg11d2gb46eg867808ec1738" version="2">Ваш опыт борьбы с заклинателями даёт вам следующие преимущества.<br><br>Разрушитель концентрации. Каждый раз, когда вы совершаете дальнобойный бросок атаки, вы нарушаете защитную магию, действующую на цель, и прерываете её концентрацию.
|
||||
|
||||
Антимагический выстрел. Когда вы совершаете критическое попадание по цели под действием вашей особенности «Выстрел в живот», вы также подавляете её способность накладывать заклинания. Пока снаряд остаётся в цели, она не может накладывать заклинания или выполнять действие «Магия».
|
||||
|
||||
Закалённый магией. Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить результат к броску, потенциально превращая провал в успех.</content>
|
||||
<content contentuid="h5ef73b58ga233g8fccg4389gcd443dd5c7d7" version="1">Закалённый магией</content>
|
||||
<content contentuid="h672e82feg2734g7895ga227gd76ffbfded41" version="1">Примите облик гигантского барсука, который может <LSTag Type="Spell" Tooltip="Target_Burrow_GiantBadger">Зарываться</LSTag> в землю.</content>
|
||||
<content contentuid="h6827c922gd910gca98gee99gdbd9b0f178b1" version="1">Примите облик глубинного ротэ, который может накладывать <LSTag Type="Spell" Tooltip="Target_DancingLights">Пляшущие огоньки</LSTag> и <LSTag Type="Spell" Tooltip="Rush_Rush_DeepRothe">Таранить</LSTag> врагов.</content>
|
||||
<content contentuid="h6c004994g2426gc2fbg7e1eg48891af8e993" version="1">Примите облик зловещей вороны, которая может оставаться незаметной и <LSTag Type="Spell" Tooltip="Target_RendVision_Raven_Summon">Ослеплять</LSTag> врагов.</content>
|
||||
<content contentuid="h6c75a711gccb2g34c5g5489gfbaf6012200e" version="2">Оружие пакта: урон некротической энергией</content>
|
||||
<content contentuid="h8697b53cg246dg4397g9db8gc35b4035bc5b" version="1">Связать оружие пакта (урон излучением)</content>
|
||||
<content contentuid="h93c42699gb5efg50eeg700ag91b482874bb9" version="1">Связать оружие пакта (урон некротической энергией)</content>
|
||||
<content contentuid="h9f0fbf45g41d6gb607g5104g558e4732b4de" version="1">Примите облик гигантского паука, который может <LSTag Type="Spell" Tooltip="Target_Web_Spider">Опутывать паутиной</LSTag> врагов.</content>
|
||||
<content contentuid="ha2a934dbg1420gf4c4g7431gcf508603657a" version="2">Оружие пакта: урон излучением</content>
|
||||
<content contentuid="ha7014351gc454g307agb356g37500f5752d7" version="1">Уровень 10: Антимаг</content>
|
||||
<content contentuid="hbe8ae593g19c8gc31cg51cdg845945c5b41b" version="1">Связать оружие пакта (урон психической энергией)</content>
|
||||
<content contentuid="hc4d3f811g2a89g0e6dg5219gcf3bfa3167c3" version="1">Примите облик зловещего волка, который может <LSTag Type="Spell" Tooltip="Shout_PackHowl_Wolf_Dire">Воодушевлять</LSTag> союзников и <LSTag Type="Spell" Tooltip="Target_Bite_Wolf_Dire_Wildshape">Отвлекать</LSTag> врагов.</content>
|
||||
<content contentuid="hcb4e031eg6175g6174g6466gcc87b839601d" version="1">Когда вы попадаете атакой «Пальцы-пистолеты» по цели, вы можете потратить одну Кость риска и добавить её к броску урона.</content>
|
||||
<content contentuid="hcd88f337g7b53g35deg791bgf37bf6314ed5" version="2">Оружие пакта: урон психической энергией</content>
|
||||
<content contentuid="hcdf4a03bg59c5gb1b2g3204g37cf5a2339c0" version="1">Мистический выстрел</content>
|
||||
<content contentuid="hd3836cebg50efg96d2g4884gc1006821d8ef" version="1">Когда в свой ход вы совершаете действие «Атака», вы можете заменить одну из атак накладыванием одного из ваших фокусов волшебника со временем накладывания в одно действие.</content>
|
||||
<content contentuid="hd771ba94g1544ge8bage691g562f72e6c038" version="1">Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить выпавшее значение к броску, потенциально превращая провал в успех.</content>
|
||||
<content contentuid="he8e15200g5d16g3cfag9fd3g57eec75fb43b" version="1">Примите облик кота, который может оставаться незаметным и <LSTag Type="Spell" Tooltip="Shout_Distract_Cat_Summon">Мяукать</LSTag>, отвлекая врагов.</content>
|
||||
<content contentuid="he97ec05ag27f2gdd59g1ebag5d02ea37d3df" version="1">Не может накладывать заклинания или выполнять действие «Магия»</content>
|
||||
<content contentuid="hec2215cegc8c3g171bg67cdg7de02f56bba6" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</content>
|
||||
<content contentuid="hf051b82fgb193ga37bg540ege6200dcbd93e" version="1">Вы можете использовать магию вместо огнестрельного оружия.
|
||||
|
||||
Пальцы-пистолеты. Вы изучаете фокус «Пальцы-пистолеты».
|
||||
|
||||
Мистический выстрел. Когда вы попадаете атакой «Пальцы-пистолеты» по цели, вы можете потратить одну Кость риска и добавить её к броску урона.</content>
|
||||
<content contentuid="hf3b2ff84g1b07ga3eag2125ge551077a6e4d" version="1">Оружие пакта</content>
|
||||
<content contentuid="hfee38a66g5042g2c16g753dg9b88aa769f46" version="1">Антимагический выстрел</content>
|
||||
</contentList>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<save>
|
||||
<version major="4" minor="8" revision="0" build="500"/>
|
||||
<region id="Config">
|
||||
<node id="root">
|
||||
<children>
|
||||
<node id="Conflicts"/>
|
||||
<node id="Dependencies">
|
||||
<children>
|
||||
<node id="ModuleShortDesc">
|
||||
<attribute id="Folder" type="LSString" value=""/>
|
||||
<attribute id="MD5" type="LSString" value=""/>
|
||||
<attribute id="Folder" type="LSString" value="DnD2024_897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||
<attribute id="MD5" type="LSString" value="ac497558b070a635abecd7d23d4a3125"/>
|
||||
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND"/>
|
||||
<attribute id="PublishHandle" type="uint64" value="0"/>
|
||||
<attribute id="PublishHandle" type="uint64" value="4419649"/>
|
||||
<attribute id="UUID" type="guid" value="897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
||||
<attribute id="Version64" type="int64" value="144396678084952064"/>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
<node id="ModuleInfo">
|
||||
<attribute id="Author" type="LSString" value="MikhailRaw"/>
|
||||
<attribute id="CharacterCreationLevelName" type="FixedString" value=""/>
|
||||
<attribute id="Description" type="LSString" value="Русская локализация мода, который добавляет и обновляет контент в соответствии с правилами DnD 5.5e и другими источниками, включая предыстории, классы, таланты, расы, заклинания и многое другое. Это отдельный мод локализации и он требует установленный оригинальный мод."/>
|
||||
<attribute id="FileSize" type="uint64" value="0"/>
|
||||
<attribute id="Description" type="LSString" value="Русский перевод мода DnD 5.5e All-in-One BEYOND. Перевод ещё в разработке: AI помогает быстро обновлять тексты, а финальные правки и качество мы проверяем вручную."/>
|
||||
<attribute id="FileSize" type="uint64" value="2488095"/>
|
||||
<attribute id="Folder" type="LSString" value="DnD 5.5e AIO Russian"/>
|
||||
<attribute id="LobbyLevelName" type="FixedString" value=""/>
|
||||
<attribute id="MD5" type="LSString" value=""/>
|
||||
<attribute id="MD5" type="LSString" value="c0a8f3412870277331306e0719fc6f77"/>
|
||||
<attribute id="MenuLevelName" type="FixedString" value=""/>
|
||||
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND Russian Localization"/>
|
||||
<attribute id="NumPlayers" type="uint8" value="4"/>
|
||||
<attribute id="PhotoBooth" type="FixedString" value=""/>
|
||||
<attribute id="PublishHandle" type="uint64" value="0"/>
|
||||
<attribute id="PublishHandle" type="uint64" value="5965149"/>
|
||||
<attribute id="StartupLevelName" type="FixedString" value=""/>
|
||||
<attribute id="UUID" type="FixedString" value="6401e84d-daf2-416d-adeb-99c03a2487a6"/>
|
||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
||||
<attribute id="Version64" type="int64" value="281481419161600"/>
|
||||
<children>
|
||||
<node id="PublishVersion">
|
||||
<attribute id="Version64" type="int64" value="36028797018963968"/>
|
||||
<attribute id="Version64" type="int64" value="281477124194304"/>
|
||||
</node>
|
||||
<node id="Scripts">
|
||||
<children>
|
||||
<node id="Script">
|
||||
<attribute id="UUID" type="FixedString" value="1953f77d-a201-45d7-a194-9b84c34b8461"/>
|
||||
<children>
|
||||
<node id="Parameters">
|
||||
<children>
|
||||
<node id="Parameter">
|
||||
<attribute id="MapKey" type="FixedString" value="HardcoreOnly"/>
|
||||
<attribute id="Type" type="int32" value="1"/>
|
||||
<attribute id="Value" type="LSString" value="0"/>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
<node id="Script">
|
||||
<attribute id="UUID" type="FixedString" value="0d6510f5-50a3-4ecd-83d8-134c9a640324"/>
|
||||
<children>
|
||||
<node id="Parameters">
|
||||
<children>
|
||||
<node id="Parameter">
|
||||
<attribute id="MapKey" type="FixedString" value="HardcoreOnly"/>
|
||||
<attribute id="Type" type="int32" value="1"/>
|
||||
<attribute id="Value" type="LSString" value="0"/>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
</children>
|
||||
</node>
|
||||
|
||||
186
scripts/apply-translation-edits.ps1
Normal file
186
scripts/apply-translation-edits.ps1
Normal file
@@ -0,0 +1,186 @@
|
||||
param(
|
||||
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$EditsPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Read-XmlDocument {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||
throw "XML file was not found: '$resolvedPath'."
|
||||
}
|
||||
|
||||
[xml]$xml = Get-Content -LiteralPath $resolvedPath -Raw
|
||||
if ($null -eq $xml.SelectSingleNode('/contentList')) {
|
||||
throw "XML file does not contain '/contentList': '$resolvedPath'."
|
||||
}
|
||||
|
||||
return @{
|
||||
Path = $resolvedPath
|
||||
Xml = $xml
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ContentNodeMap {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[xml]$Xml
|
||||
)
|
||||
|
||||
$map = @{}
|
||||
foreach ($node in $Xml.SelectNodes('/contentList/content')) {
|
||||
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($map.ContainsKey($contentUid)) {
|
||||
throw "Duplicate contentuid found in target XML: '$contentUid'."
|
||||
}
|
||||
|
||||
$map[$contentUid] = $node
|
||||
}
|
||||
|
||||
return $map
|
||||
}
|
||||
|
||||
$russianDocument = Read-XmlDocument -Path $RussianPath
|
||||
$temporaryRussianPath = "$($russianDocument.Path).tmp"
|
||||
$validateScriptPath = Join-Path $PSScriptRoot "validate-translation-xml.ps1"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $validateScriptPath)) {
|
||||
throw "Validation script was not found: '$validateScriptPath'."
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $russianDocument.Path -Destination $temporaryRussianPath -Force
|
||||
$russianDocument = Read-XmlDocument -Path $temporaryRussianPath
|
||||
$resolvedEditsPath = [System.IO.Path]::GetFullPath($EditsPath)
|
||||
if (-not (Test-Path -LiteralPath $resolvedEditsPath)) {
|
||||
throw "Edits file was not found: '$resolvedEditsPath'."
|
||||
}
|
||||
|
||||
$edits = Get-Content -LiteralPath $resolvedEditsPath -Raw | ConvertFrom-Json -Depth 10
|
||||
if ($null -eq $edits) {
|
||||
throw "Edits file is empty or invalid JSON: '$resolvedEditsPath'."
|
||||
}
|
||||
|
||||
$contentListNode = $russianDocument.Xml.SelectSingleNode('/contentList')
|
||||
if ($null -eq $contentListNode) {
|
||||
throw "Target russian.xml does not contain '/contentList': '$($russianDocument.Path)'."
|
||||
}
|
||||
|
||||
$nodeMap = Get-ContentNodeMap -Xml $russianDocument.Xml
|
||||
$updatedEntries = New-Object System.Collections.Generic.List[string]
|
||||
$addedEntries = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
$updates = @()
|
||||
if ($edits.PSObject.Properties.Name -contains "updates" -and $null -ne $edits.updates) {
|
||||
$updates = @($edits.updates)
|
||||
}
|
||||
|
||||
foreach ($edit in $updates) {
|
||||
$contentUid = [string]$edit.contentuid
|
||||
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||
throw "Each update entry must contain non-empty 'contentuid'."
|
||||
}
|
||||
|
||||
if (-not $nodeMap.ContainsKey($contentUid)) {
|
||||
throw "Target russian.xml does not contain contentuid '$contentUid' for update."
|
||||
}
|
||||
|
||||
$node = $nodeMap[$contentUid]
|
||||
if ($edit.PSObject.Properties.Name -contains "version" -and -not [string]::IsNullOrWhiteSpace([string]$edit.version)) {
|
||||
$node.SetAttribute("version", [string]$edit.version)
|
||||
}
|
||||
|
||||
if ($edit.PSObject.Properties.Name -contains "text") {
|
||||
$node.InnerText = [string]$edit.text
|
||||
}
|
||||
|
||||
$updatedEntries.Add($contentUid) | Out-Null
|
||||
}
|
||||
|
||||
$adds = @()
|
||||
if ($edits.PSObject.Properties.Name -contains "adds" -and $null -ne $edits.adds) {
|
||||
$adds = @($edits.adds)
|
||||
}
|
||||
|
||||
if (($updates.Count -eq 0) -and ($adds.Count -eq 0)) {
|
||||
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||
}
|
||||
Write-Host "[apply-translation-edits.ps1] No edits requested. Original russian.xml left unchanged."
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($edit in $adds) {
|
||||
$contentUid = [string]$edit.contentuid
|
||||
$version = [string]$edit.version
|
||||
$text = [string]$edit.text
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||
throw "Each add entry must contain non-empty 'contentuid'."
|
||||
}
|
||||
|
||||
if ($nodeMap.ContainsKey($contentUid)) {
|
||||
throw "Target russian.xml already contains contentuid '$contentUid'; use 'updates' instead of 'adds'."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
throw "Add entry '$contentUid' must contain non-empty 'version'."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($text)) {
|
||||
throw "Add entry '$contentUid' must contain non-empty 'text'."
|
||||
}
|
||||
|
||||
$newNode = $russianDocument.Xml.CreateElement("content")
|
||||
$newNode.SetAttribute("contentuid", $contentUid)
|
||||
$newNode.SetAttribute("version", $version)
|
||||
$newNode.InnerText = $text
|
||||
[void]$contentListNode.AppendChild($newNode)
|
||||
$nodeMap[$contentUid] = $newNode
|
||||
$addedEntries.Add($contentUid) | Out-Null
|
||||
}
|
||||
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||
$settings.Indent = $true
|
||||
$settings.IndentChars = " "
|
||||
$settings.NewLineChars = "`n"
|
||||
$settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace
|
||||
|
||||
$writer = [System.Xml.XmlWriter]::Create($russianDocument.Path, $settings)
|
||||
try {
|
||||
$russianDocument.Xml.WriteTo($writer)
|
||||
} finally {
|
||||
$writer.Dispose()
|
||||
}
|
||||
|
||||
try {
|
||||
& $validateScriptPath -XmlPath $temporaryRussianPath
|
||||
Move-Item -LiteralPath $temporaryRussianPath -Destination $RussianPath -Force
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $temporaryRussianPath) {
|
||||
Remove-Item -LiteralPath $temporaryRussianPath -Force
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[apply-translation-edits.ps1] Updated entries: $($updatedEntries.Count); Added entries: $($addedEntries.Count)."
|
||||
if ($updatedEntries.Count -gt 0) {
|
||||
Write-Host "[apply-translation-edits.ps1] Updated contentuid: $($updatedEntries -join ', ')"
|
||||
}
|
||||
if ($addedEntries.Count -gt 0) {
|
||||
Write-Host "[apply-translation-edits.ps1] Added contentuid: $($addedEntries -join ', ')"
|
||||
}
|
||||
@@ -113,6 +113,8 @@ if (Test-Path -LiteralPath $tempPackagePath) {
|
||||
Remove-Item -LiteralPath $tempPackagePath -Force
|
||||
}
|
||||
|
||||
# CI quirk: Divine can occasionally emit a broken ~48-byte package for some source roots.
|
||||
# Mitigation: try staged/mods/workspace sources and accept only outputs that look valid by size.
|
||||
$packageAttempts = @(
|
||||
[ordered]@{ Name = "staging-root"; Source = $stagingPath },
|
||||
[ordered]@{ Name = "mods-root"; Source = $modsPath },
|
||||
|
||||
203
scripts/compare-translation.ps1
Normal file
203
scripts/compare-translation.ps1
Normal file
@@ -0,0 +1,203 @@
|
||||
param(
|
||||
[string]$EnglishPath = ".cache/upstream/english.xml",
|
||||
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||
[string]$OutputDir = "build/translation-diff"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-LocalizationEntries {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||
throw "Localization XML was not found: '$resolvedPath'."
|
||||
}
|
||||
|
||||
[xml]$xml = Get-Content -LiteralPath $resolvedPath -Raw
|
||||
$nodes = $xml.SelectNodes('/contentList/content')
|
||||
if ($null -eq $nodes) {
|
||||
throw "Localization XML does not contain '/contentList/content' nodes: '$resolvedPath'."
|
||||
}
|
||||
|
||||
$entries = @{}
|
||||
foreach ($node in $nodes) {
|
||||
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$entries[$contentUid] = [ordered]@{
|
||||
contentuid = $contentUid
|
||||
version = [string]$node.GetAttribute("version")
|
||||
text = [string]$node.InnerText
|
||||
}
|
||||
}
|
||||
|
||||
return $entries
|
||||
}
|
||||
|
||||
$resolvedOutputDir = [System.IO.Path]::GetFullPath($OutputDir)
|
||||
New-Item -ItemType Directory -Path $resolvedOutputDir -Force | Out-Null
|
||||
|
||||
$englishEntries = Get-LocalizationEntries -Path $EnglishPath
|
||||
$russianEntries = Get-LocalizationEntries -Path $RussianPath
|
||||
|
||||
$englishKeys = [System.Collections.Generic.HashSet[string]]::new([string[]]$englishEntries.Keys)
|
||||
$russianKeys = [System.Collections.Generic.HashSet[string]]::new([string[]]$russianEntries.Keys)
|
||||
|
||||
$missingInRussian = New-Object System.Collections.Generic.List[object]
|
||||
$versionMismatch = New-Object System.Collections.Generic.List[object]
|
||||
$staleOnlyInRussian = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($contentUid in ($englishEntries.Keys | Sort-Object)) {
|
||||
$englishEntry = $englishEntries[$contentUid]
|
||||
if (-not $russianEntries.ContainsKey($contentUid)) {
|
||||
$missingInRussian.Add([pscustomobject]@{
|
||||
contentuid = $contentUid
|
||||
englishVersion = $englishEntry.version
|
||||
englishText = $englishEntry.text
|
||||
}) | Out-Null
|
||||
continue
|
||||
}
|
||||
|
||||
$russianEntry = $russianEntries[$contentUid]
|
||||
if ($englishEntry.version -ne $russianEntry.version) {
|
||||
$versionMismatch.Add([pscustomobject]@{
|
||||
contentuid = $contentUid
|
||||
englishVersion = $englishEntry.version
|
||||
russianVersion = $russianEntry.version
|
||||
englishText = $englishEntry.text
|
||||
russianText = $russianEntry.text
|
||||
}) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($contentUid in ($russianEntries.Keys | Sort-Object)) {
|
||||
if (-not $englishEntries.ContainsKey($contentUid)) {
|
||||
$russianEntry = $russianEntries[$contentUid]
|
||||
$staleOnlyInRussian.Add([pscustomobject]@{
|
||||
contentuid = $contentUid
|
||||
russianVersion = $russianEntry.version
|
||||
russianText = $russianEntry.text
|
||||
}) | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
generatedAt = (Get-Date).ToString("o")
|
||||
englishPath = [System.IO.Path]::GetFullPath($EnglishPath)
|
||||
russianPath = [System.IO.Path]::GetFullPath($RussianPath)
|
||||
englishCount = $englishEntries.Count
|
||||
russianCount = $russianEntries.Count
|
||||
missingInRussianCount = $missingInRussian.Count
|
||||
versionMismatchCount = $versionMismatch.Count
|
||||
staleOnlyInRussianCount = $staleOnlyInRussian.Count
|
||||
missingInRussian = $missingInRussian
|
||||
versionMismatch = $versionMismatch
|
||||
staleOnlyInRussian = $staleOnlyInRussian
|
||||
}
|
||||
|
||||
$summaryJsonPath = Join-Path $resolvedOutputDir "summary.json"
|
||||
$summaryMdPath = Join-Path $resolvedOutputDir "summary.md"
|
||||
$candidatesJsonPath = Join-Path $resolvedOutputDir "candidates.json"
|
||||
|
||||
$summary | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $summaryJsonPath -Encoding utf8
|
||||
|
||||
$candidates = [ordered]@{
|
||||
generatedAt = (Get-Date).ToString("o")
|
||||
source = [ordered]@{
|
||||
englishPath = [System.IO.Path]::GetFullPath($EnglishPath)
|
||||
russianPath = [System.IO.Path]::GetFullPath($RussianPath)
|
||||
}
|
||||
updates = @(
|
||||
$versionMismatch | ForEach-Object {
|
||||
[ordered]@{
|
||||
contentuid = $_.contentuid
|
||||
version = $_.englishVersion
|
||||
text = $_.russianText
|
||||
englishText = $_.englishText
|
||||
russianVersion = $_.russianVersion
|
||||
}
|
||||
}
|
||||
)
|
||||
adds = @(
|
||||
$missingInRussian | ForEach-Object {
|
||||
[ordered]@{
|
||||
contentuid = $_.contentuid
|
||||
version = $_.englishVersion
|
||||
text = ""
|
||||
englishText = $_.englishText
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$candidates | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $candidatesJsonPath -Encoding utf8
|
||||
|
||||
$isUpToDate = ($missingInRussian.Count -eq 0) -and ($versionMismatch.Count -eq 0) -and ($staleOnlyInRussian.Count -eq 0)
|
||||
|
||||
$mdLines = @(
|
||||
"# Translation diff summary",
|
||||
"",
|
||||
"- Generated: $($summary.generatedAt)",
|
||||
"- English entries: $($summary.englishCount)",
|
||||
"- Russian entries: $($summary.russianCount)",
|
||||
"- Missing in Russian: $($summary.missingInRussianCount)",
|
||||
"- Version mismatches: $($summary.versionMismatchCount)",
|
||||
"- Stale only in Russian: $($summary.staleOnlyInRussianCount)",
|
||||
""
|
||||
)
|
||||
|
||||
if ($isUpToDate) {
|
||||
$mdLines += "Перевод уже актуален, дополнительные действия не требуются."
|
||||
} else {
|
||||
$mdLines += ""
|
||||
$mdLines += "## Local workflow"
|
||||
$mdLines += "1. Update upstream cache: ``scripts/get-upstream-english.ps1``"
|
||||
$mdLines += "2. Refresh diff: ``scripts/compare-translation.ps1``"
|
||||
$mdLines += "3. Edit ``build/translation-diff/candidates.json``"
|
||||
$mdLines += "4. Apply changes: ``scripts/apply-translation-edits.ps1 -EditsPath build/translation-diff/candidates.json``"
|
||||
}
|
||||
|
||||
$mdLines += ""
|
||||
$mdLines += "## Missing in Russian"
|
||||
if ($missingInRussian.Count -eq 0) {
|
||||
$mdLines += "- none"
|
||||
} else {
|
||||
$mdLines += ($missingInRussian | Select-Object -First 50 | ForEach-Object {
|
||||
"- ``$($_.contentuid)`` v$($_.englishVersion): $($_.englishText)"
|
||||
})
|
||||
}
|
||||
|
||||
$mdLines += ""
|
||||
$mdLines += "## Version mismatches"
|
||||
if ($versionMismatch.Count -eq 0) {
|
||||
$mdLines += "- none"
|
||||
} else {
|
||||
$mdLines += ($versionMismatch | Select-Object -First 50 | ForEach-Object {
|
||||
"- ``$($_.contentuid)`` en=v$($_.englishVersion), ru=v$($_.russianVersion)"
|
||||
})
|
||||
}
|
||||
|
||||
$mdLines += ""
|
||||
$mdLines += "## Stale only in Russian"
|
||||
if ($staleOnlyInRussian.Count -eq 0) {
|
||||
$mdLines += "- none"
|
||||
} else {
|
||||
$mdLines += ($staleOnlyInRussian | Select-Object -First 50 | ForEach-Object {
|
||||
"- ``$($_.contentuid)`` v$($_.russianVersion): $($_.russianText)"
|
||||
})
|
||||
}
|
||||
|
||||
Set-Content -LiteralPath $summaryMdPath -Value $mdLines -Encoding utf8
|
||||
|
||||
Write-Host "[compare-translation.ps1] Summary written to '$summaryJsonPath' and '$summaryMdPath'."
|
||||
Write-Host "[compare-translation.ps1] Editable candidate file written to '$candidatesJsonPath'."
|
||||
Write-Host "[compare-translation.ps1] Missing=$($missingInRussian.Count); VersionMismatch=$($versionMismatch.Count); StaleOnlyInRussian=$($staleOnlyInRussian.Count)."
|
||||
if ($isUpToDate) {
|
||||
Write-Host "[compare-translation.ps1] Перевод уже актуален, дополнительные действия не требуются."
|
||||
}
|
||||
44
scripts/get-upstream-english.ps1
Normal file
44
scripts/get-upstream-english.ps1
Normal file
@@ -0,0 +1,44 @@
|
||||
param(
|
||||
[string]$UpstreamEnglishUrl = "https://raw.githubusercontent.com/Yoonmoonsik/dnd55e/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml",
|
||||
[string]$OutputPath = ".cache/upstream/english.xml",
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($UpstreamEnglishUrl)) {
|
||||
throw "UpstreamEnglishUrl must not be empty."
|
||||
}
|
||||
|
||||
$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
$outputDirectory = Split-Path -Parent $resolvedOutputPath
|
||||
if (-not (Test-Path -LiteralPath $outputDirectory)) {
|
||||
New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null
|
||||
}
|
||||
|
||||
$requestParameters = @{
|
||||
Uri = $UpstreamEnglishUrl
|
||||
OutFile = $resolvedOutputPath
|
||||
UseBasicParsing = $true
|
||||
TimeoutSec = 120
|
||||
}
|
||||
|
||||
if ($Force) {
|
||||
$requestParameters["Headers"] = @{
|
||||
"Cache-Control" = "no-cache"
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-WebRequest @requestParameters
|
||||
|
||||
[xml]$englishXml = Get-Content -LiteralPath $resolvedOutputPath -Raw
|
||||
$rootNode = $englishXml.SelectSingleNode('/contentList')
|
||||
if ($null -eq $rootNode) {
|
||||
throw "Downloaded file is not a valid BG3 localization XML: '$resolvedOutputPath'."
|
||||
}
|
||||
|
||||
$contentCount = $englishXml.SelectNodes('/contentList/content').Count
|
||||
$fileInfo = Get-Item -LiteralPath $resolvedOutputPath
|
||||
|
||||
Write-Host "[get-upstream-english.ps1] Saved upstream english.xml to '$resolvedOutputPath'."
|
||||
Write-Host "[get-upstream-english.ps1] Entries: $contentCount; Size: $($fileInfo.Length) bytes; Updated: $($fileInfo.LastWriteTime.ToString("o"))."
|
||||
63
scripts/set-version.ps1
Normal file
63
scripts/set-version.ps1
Normal file
@@ -0,0 +1,63 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$VersionTag,
|
||||
[string]$MetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Convert-VersionTagToVersion64 {
|
||||
param(
|
||||
[string]$Tag
|
||||
)
|
||||
|
||||
$normalized = $Tag
|
||||
if ($normalized.StartsWith("v")) {
|
||||
$normalized = $normalized.Substring(1)
|
||||
}
|
||||
|
||||
if ($normalized -notmatch '^\d+(\.\d+){0,3}$') {
|
||||
throw "Version tag '$Tag' is invalid. Expected format: vX.Y.Z or X.Y.Z"
|
||||
}
|
||||
|
||||
$parts = $normalized.Split(".")
|
||||
$numbers = @(0, 0, 0, 0)
|
||||
for ($i = 0; $i -lt $parts.Length; $i++) {
|
||||
$numbers[$i] = [int]$parts[$i]
|
||||
}
|
||||
|
||||
return ([int64]$numbers[0] -shl 55) -bor ([int64]$numbers[1] -shl 47) -bor ([int64]$numbers[2] -shl 31) -bor [int64]$numbers[3]
|
||||
}
|
||||
|
||||
$resolvedMetaPath = [System.IO.Path]::GetFullPath($MetaPath)
|
||||
if (-not (Test-Path -LiteralPath $resolvedMetaPath)) {
|
||||
throw "meta.lsx was not found: '$resolvedMetaPath'."
|
||||
}
|
||||
|
||||
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag
|
||||
$metaContent = Get-Content -LiteralPath $resolvedMetaPath -Raw
|
||||
[xml]$metaXml = $metaContent
|
||||
|
||||
# Explicitly target ModuleInfo/Version64 via XML path to avoid touching Dependencies/PublishVersion.
|
||||
$moduleInfoVersionNode = $metaXml.SelectSingleNode('/save/region/node/children/node[@id="ModuleInfo"]/attribute[@id="Version64" and @type="int64"]')
|
||||
if ($null -eq $moduleInfoVersionNode) {
|
||||
throw "ModuleInfo/Version64 attribute was not found in '$resolvedMetaPath'."
|
||||
}
|
||||
|
||||
# Replace only the Version64 attribute that appears inside ModuleInfo before its <children> block.
|
||||
$moduleInfoVersionPattern = '(?s)(<node id="ModuleInfo">\s*(?:(?!<children>).)*?<attribute id="Version64" type="int64" value=")\d+("/>)'
|
||||
if ($metaContent -notmatch $moduleInfoVersionPattern) {
|
||||
throw "ModuleInfo/Version64 attribute was not found in '$resolvedMetaPath'."
|
||||
}
|
||||
|
||||
$updatedMeta = [regex]::Replace(
|
||||
$metaContent,
|
||||
$moduleInfoVersionPattern,
|
||||
"`${1}$resolvedVersion64`${2}",
|
||||
1
|
||||
)
|
||||
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($resolvedMetaPath, $updatedMeta, $utf8Bom)
|
||||
|
||||
Write-Host "[set-version.ps1] Updated '$resolvedMetaPath' to Version64=$resolvedVersion64 (from tag '$VersionTag')."
|
||||
91
scripts/sync-parent-meta.ps1
Normal file
91
scripts/sync-parent-meta.ps1
Normal file
@@ -0,0 +1,91 @@
|
||||
param(
|
||||
[string]$ParentMetaUrl = "https://raw.githubusercontent.com/Yoonmoonsik/dnd55e/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/meta.lsx",
|
||||
[string]$TargetMetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$resolvedTargetMetaPath = [System.IO.Path]::GetFullPath($TargetMetaPath)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedTargetMetaPath)) {
|
||||
throw "Target meta.lsx was not found: '$resolvedTargetMetaPath'."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ParentMetaUrl)) {
|
||||
throw "ParentMetaUrl must not be empty."
|
||||
}
|
||||
|
||||
try {
|
||||
$parentResponse = Invoke-WebRequest -Uri $ParentMetaUrl -UseBasicParsing -TimeoutSec 60
|
||||
} catch {
|
||||
throw "Failed to download parent meta.lsx from '$ParentMetaUrl': $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
$parentRaw = $parentResponse.Content
|
||||
$targetRaw = Get-Content -LiteralPath $resolvedTargetMetaPath -Raw
|
||||
|
||||
[xml]$parentXml = $parentRaw
|
||||
[xml]$targetXml = $targetRaw
|
||||
|
||||
$parentModuleInfo = $parentXml.SelectSingleNode('/save/region/node/children/node[@id="ModuleInfo"]')
|
||||
if ($null -eq $parentModuleInfo) {
|
||||
throw "ModuleInfo node was not found in parent meta downloaded from '$ParentMetaUrl'."
|
||||
}
|
||||
|
||||
$requiredFields = @("Folder", "MD5", "Name", "PublishHandle", "UUID", "Version64")
|
||||
$sourceValues = @{}
|
||||
|
||||
foreach ($field in $requiredFields) {
|
||||
$node = $parentModuleInfo.SelectSingleNode("attribute[@id='$field']")
|
||||
if ($null -eq $node) {
|
||||
throw "Required parent ModuleInfo attribute '$field' is missing in meta downloaded from '$ParentMetaUrl'."
|
||||
}
|
||||
|
||||
$value = $node.GetAttribute("value")
|
||||
if ([string]::IsNullOrWhiteSpace($value)) {
|
||||
throw "Required parent ModuleInfo attribute '$field' has empty value in meta downloaded from '$ParentMetaUrl'."
|
||||
}
|
||||
|
||||
$sourceValues[$field] = $value
|
||||
}
|
||||
|
||||
$targetDependencyNode = $targetXml.SelectSingleNode('/save/region/node/children/node[@id="Dependencies"]/children/node[@id="ModuleShortDesc"]')
|
||||
if ($null -eq $targetDependencyNode) {
|
||||
throw "Dependencies/ModuleShortDesc node was not found in target meta: '$resolvedTargetMetaPath'."
|
||||
}
|
||||
|
||||
$changedFields = @()
|
||||
foreach ($field in $requiredFields) {
|
||||
$targetAttr = $targetDependencyNode.SelectSingleNode("attribute[@id='$field']")
|
||||
if ($null -eq $targetAttr) {
|
||||
throw "Target Dependencies/ModuleShortDesc attribute '$field' is missing in '$resolvedTargetMetaPath'."
|
||||
}
|
||||
|
||||
$currentValue = $targetAttr.GetAttribute("value")
|
||||
$newValue = [string]$sourceValues[$field]
|
||||
if ($currentValue -ne $newValue) {
|
||||
$targetAttr.SetAttribute("value", $newValue)
|
||||
$changedFields += $field
|
||||
}
|
||||
}
|
||||
|
||||
if ($changedFields.Count -eq 0) {
|
||||
Write-Host "[sync-parent-meta.ps1] No changes needed. Target dependency data is already up to date."
|
||||
} else {
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $utf8Bom
|
||||
$settings.Indent = $true
|
||||
$settings.IndentChars = " "
|
||||
$settings.NewLineChars = "`n"
|
||||
$settings.NewLineHandling = [System.Xml.NewLineHandling]::Replace
|
||||
|
||||
$writer = [System.Xml.XmlWriter]::Create($resolvedTargetMetaPath, $settings)
|
||||
try {
|
||||
$targetXml.WriteTo($writer)
|
||||
} finally {
|
||||
$writer.Dispose()
|
||||
}
|
||||
|
||||
Write-Host ("[sync-parent-meta.ps1] Updated fields: " + ($changedFields -join ", "))
|
||||
}
|
||||
70
scripts/update-translation.ps1
Normal file
70
scripts/update-translation.ps1
Normal file
@@ -0,0 +1,70 @@
|
||||
param(
|
||||
[string]$RussianPath = "Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml",
|
||||
[string]$EnglishPath = ".cache/upstream/english.xml",
|
||||
[string]$OutputDir = "build/translation-diff",
|
||||
[string]$EditsPath = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$getUpstreamScriptPath = Join-Path $PSScriptRoot "get-upstream-english.ps1"
|
||||
$compareScriptPath = Join-Path $PSScriptRoot "compare-translation.ps1"
|
||||
$applyScriptPath = Join-Path $PSScriptRoot "apply-translation-edits.ps1"
|
||||
|
||||
foreach ($scriptPath in @($getUpstreamScriptPath, $compareScriptPath, $applyScriptPath)) {
|
||||
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
||||
throw "Required script was not found: '$scriptPath'."
|
||||
}
|
||||
}
|
||||
|
||||
$resolvedOutputDir = [System.IO.Path]::GetFullPath($OutputDir)
|
||||
$resolvedProvidedEditsPath = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($EditsPath)) {
|
||||
$resolvedProvidedEditsPath = [System.IO.Path]::GetFullPath($EditsPath)
|
||||
}
|
||||
$workingDiffDir = Join-Path $env:TEMP ("bg3-translation-update-" + [guid]::NewGuid().ToString("N"))
|
||||
New-Item -ItemType Directory -Path $workingDiffDir -Force | Out-Null
|
||||
|
||||
try {
|
||||
& $getUpstreamScriptPath -OutputPath $EnglishPath -Force
|
||||
& $compareScriptPath -EnglishPath $EnglishPath -RussianPath $RussianPath -OutputDir $workingDiffDir
|
||||
|
||||
New-Item -ItemType Directory -Path $resolvedOutputDir -Force | Out-Null
|
||||
Get-ChildItem -LiteralPath $workingDiffDir | ForEach-Object {
|
||||
$destinationPath = Join-Path $resolvedOutputDir $_.Name
|
||||
if ($resolvedProvidedEditsPath -and ([System.IO.Path]::GetFullPath($destinationPath) -eq $resolvedProvidedEditsPath) -and (Test-Path -LiteralPath $resolvedProvidedEditsPath)) {
|
||||
return
|
||||
}
|
||||
Copy-Item -LiteralPath $_.FullName -Destination $resolvedOutputDir -Recurse -Force
|
||||
}
|
||||
|
||||
$summaryJsonPath = Join-Path $workingDiffDir "summary.json"
|
||||
if (-not (Test-Path -LiteralPath $summaryJsonPath)) {
|
||||
throw "Translation summary was not found: '$summaryJsonPath'."
|
||||
}
|
||||
|
||||
$summary = Get-Content -LiteralPath $summaryJsonPath -Raw | ConvertFrom-Json -Depth 10
|
||||
$hasDiff = ($summary.missingInRussianCount -gt 0) -or ($summary.versionMismatchCount -gt 0) -or ($summary.staleOnlyInRussianCount -gt 0)
|
||||
|
||||
if (-not $hasDiff) {
|
||||
Write-Host "[update-translation.ps1] Перевод уже актуален, дополнительные действия не требуются."
|
||||
return
|
||||
}
|
||||
|
||||
$effectiveEditsPath = $EditsPath
|
||||
if ([string]::IsNullOrWhiteSpace($effectiveEditsPath)) {
|
||||
$effectiveEditsPath = Join-Path $resolvedOutputDir "candidates.json"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $effectiveEditsPath)) {
|
||||
throw "Translation changes were found. Prepare edits in '$([System.IO.Path]::GetFullPath((Join-Path $resolvedOutputDir "candidates.json")))' and rerun update with '-EditsPath'."
|
||||
}
|
||||
|
||||
& $applyScriptPath -RussianPath $RussianPath -EditsPath $effectiveEditsPath
|
||||
|
||||
Write-Host "[update-translation.ps1] Обновление перевода завершено. Результат записан в '$([System.IO.Path]::GetFullPath($RussianPath))'."
|
||||
} finally {
|
||||
if (Test-Path -LiteralPath $workingDiffDir) {
|
||||
Remove-Item -LiteralPath $workingDiffDir -Recurse -Force
|
||||
}
|
||||
}
|
||||
41
scripts/validate-translation-xml.ps1
Normal file
41
scripts/validate-translation-xml.ps1
Normal file
@@ -0,0 +1,41 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$XmlPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$resolvedXmlPath = [System.IO.Path]::GetFullPath($XmlPath)
|
||||
if (-not (Test-Path -LiteralPath $resolvedXmlPath)) {
|
||||
throw "XML file was not found: '$resolvedXmlPath'."
|
||||
}
|
||||
|
||||
[xml]$xml = Get-Content -LiteralPath $resolvedXmlPath -Raw
|
||||
$contentListNode = $xml.SelectSingleNode('/contentList')
|
||||
if ($null -eq $contentListNode) {
|
||||
throw "XML validation failed: missing '/contentList' in '$resolvedXmlPath'."
|
||||
}
|
||||
|
||||
$contentNodes = $xml.SelectNodes('/contentList/content')
|
||||
if ($null -eq $contentNodes -or $contentNodes.Count -lt 1) {
|
||||
throw "XML validation failed: no '/contentList/content' entries found in '$resolvedXmlPath'."
|
||||
}
|
||||
|
||||
$seen = [System.Collections.Generic.HashSet[string]]::new()
|
||||
foreach ($node in $contentNodes) {
|
||||
$contentUid = [string]$node.GetAttribute("contentuid")
|
||||
if ([string]::IsNullOrWhiteSpace($contentUid)) {
|
||||
throw "XML validation failed: found content node without 'contentuid' in '$resolvedXmlPath'."
|
||||
}
|
||||
|
||||
if (-not $seen.Add($contentUid)) {
|
||||
throw "XML validation failed: duplicate contentuid '$contentUid' in '$resolvedXmlPath'."
|
||||
}
|
||||
|
||||
$version = [string]$node.GetAttribute("version")
|
||||
if ([string]::IsNullOrWhiteSpace($version)) {
|
||||
throw "XML validation failed: contentuid '$contentUid' has empty 'version' in '$resolvedXmlPath'."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[validate-translation-xml.ps1] XML is valid: '$resolvedXmlPath'. Entries=$($contentNodes.Count)."
|
||||
Reference in New Issue
Block a user