19 Commits
v0.2.5 ... main

Author SHA1 Message Date
8f8233ab0b Добавлены локальный env и Telegram-уведомления для CI 2026-04-11 20:03:24 +03:00
af1fd046d4 Уточнено правило завершения работы в fix и feat ветках 2026-04-11 19:35:19 +03:00
d82c627bb1 Подготовлена поддержка тестовых релизных тегов 2026-04-11 18:52:08 +03:00
d17bd723e3 Добавлены уведомления о релизной сборке в Telegram 2026-04-11 18:23:49 +03:00
fe8c50ff80 Поднята версия мода до v0.2.7 2026-04-11 00:19:07 +03:00
f9289b4384 Слита ветка feat/update-translation в main 2026-04-11 00:16:06 +03:00
5ac424770d Merge remote-tracking branch 'origin/main' into feat/update-translation
# Conflicts:
#	Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml
2026-04-11 00:13:26 +03:00
a29c22af12 Исправлены формулировки перевода иллриггера 2026-04-11 00:11:52 +03:00
b684bf195e Merge pull request #1 from Underslumber/fix/update-translation
Fix/update translation
2026-04-11 00:02:05 +03:00
39ac7a54ce Обновлен перевод класса Illrigger 2026-04-11 00:01:05 +03:00
78a9840349 Обновлен перевод и исправлено применение JSON-правок 2026-04-10 23:55:57 +03:00
92c78f0813 Уточнены правила AGENTS.md по ссылкам, триггерам CI и approval gates 2026-04-10 22:53:40 +03:00
07c7fc8aa7 Поднята версия релиза до v0.2.6 2026-04-10 22:52:43 +03:00
cb37f422ac Merge remote-tracking branch 'origin/main' 2026-04-10 22:50:04 +03:00
72bab59520 Fix 2026-04-10 22:29:04 +03:00
1ca6d579da Слияние изменений из Gitea 2026-04-10 19:38:03 +03:00
6ede25dc35 Обновлена версия мода до v0.2.4 2026-04-10 15:26:10 +03:00
de85438afe Добавлен GitHub Actions для сборки и релизов 2026-04-10 14:53:04 +03:00
111cf8c269 Восстановлены потерянные правила при рефакторинге AGENTS.md 2026-04-10 12:37:18 +03:00
11 changed files with 757 additions and 105 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
BOT_TOKEN=
TG_CHAT_ID=
TG_THREAD_ID=

View File

@@ -13,6 +13,9 @@ jobs:
build:
runs-on:
- win11
env:
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
TG_THREAD_ID: ${{ secrets.TG_THREAD_ID }}
defaults:
run:
@@ -40,6 +43,7 @@ jobs:
}
git -c "http.extraHeader=Authorization: Basic $authBasic" fetch --depth 1 origin $repoRef
git -c "http.extraHeader=Authorization: Basic $authBasic" fetch --force --tags origin
git checkout --force FETCH_HEAD
if (-not (Test-Path -LiteralPath "Mods\\DnD 5.5e AIO Russian\\Localization\\Russian\\russian.xml")) {
@@ -49,6 +53,17 @@ jobs:
New-Item -ItemType Directory -Path ".tools\\lslib" -Force | Out-Null
New-Item -ItemType Directory -Path "build" -Force | Out-Null
- name: Notify Telegram about build start
if: startsWith(gitea.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$runUrl = "${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}"
$text = "⏳ <b>Старт сборки релиза</b>`n<b>Репозиторий:</b> <code>${{ gitea.repository }}</code>`n<b>Тег:</b> <code>${{ gitea.ref_name }}</code>`n<a href=`"$runUrl`">Открыть лог сборки</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text -DisableNotification
- name: Download latest LSLib release
run: |
$ErrorActionPreference = "Stop"
@@ -109,6 +124,7 @@ jobs:
throw "Release archive was not found at '$zipPath'."
}
$isPrerelease = $tagName -match '^v\d+\.\d+\.\d+-'
$owner = $repoParts[0]
$repo = $repoParts[1]
$apiBase = "$serverUrl/api/v1/repos/$owner/$repo"
@@ -137,10 +153,19 @@ jobs:
name = $tagName
target_commitish = "${{ gitea.sha }}"
draft = $false
prerelease = $false
prerelease = $isPrerelease
} | ConvertTo-Json
$release = Invoke-RestMethod -Method Post -Uri "$apiBase/releases" -Headers $headers -ContentType "application/json" -Body $releaseBody
} elseif ($release.prerelease -ne $isPrerelease) {
$releaseBody = @{
tag_name = $tagName
target_commitish = "${{ gitea.sha }}"
draft = $false
prerelease = $isPrerelease
} | ConvertTo-Json
$release = Invoke-RestMethod -Method Patch -Uri "$apiBase/releases/$($release.id)" -Headers $headers -ContentType "application/json" -Body $releaseBody
}
$existingAsset = $null
@@ -158,3 +183,25 @@ jobs:
}
Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$apiBase/releases/$($release.id)/assets?name=$([uri]::EscapeDataString($assetName))" -Headers $uploadHeaders -ContentType "application/octet-stream" -InFile $zipPath
- name: Notify Telegram about build success
if: success() && startsWith(gitea.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$releaseUrl = "${{ gitea.server_url }}/${{ gitea.repository }}/releases/tag/${{ gitea.ref_name }}"
$text = "🏁 <b>Релиз собран успешно</b>`n<b>Тег:</b> <code>${{ gitea.ref_name }}</code>`n<a href=`"$releaseUrl`">Открыть готовый релиз</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text -DisableNotification
- name: Notify Telegram about build failure
if: failure() && startsWith(gitea.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$runUrl = "${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}"
$text = "❌ <b>Сборка релиза завершилась ошибкой</b>`n<b>Тег:</b> <code>${{ gitea.ref_name }}</code>`n<a href=`"$runUrl`">Открыть лог сборки</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text

186
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,186 @@
name: Build Mod Package
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: windows-latest
environment: Configure TgBot
env:
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
TG_THREAD_ID: ${{ secrets.TG_THREAD_ID }}
defaults:
run:
shell: pwsh
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Prepare workspace
run: |
$ErrorActionPreference = "Stop"
if (-not (Test-Path -LiteralPath "Mods\\DnD 5.5e AIO Russian\\Localization\\Russian\\russian.xml")) {
throw "Repository sources are not available in the runner workspace."
}
New-Item -ItemType Directory -Path ".tools\\lslib" -Force | Out-Null
New-Item -ItemType Directory -Path "build" -Force | Out-Null
- name: Resolve version tag
id: vars
run: |
$ErrorActionPreference = "Stop"
$versionTag = ""
$isPrerelease = "false"
if ($env:GITHUB_REF -like "refs/tags/v*") {
$versionTag = $env:GITHUB_REF_NAME
if ($versionTag -match '^v\d+\.\d+\.\d+-') {
$isPrerelease = "true"
}
}
"version_tag=$versionTag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
"is_prerelease=$isPrerelease" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Notify Telegram about build start
if: startsWith(github.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$target = if ("${{ steps.vars.outputs.version_tag }}") { "${{ steps.vars.outputs.version_tag }}" } else { "${{ github.ref_name }}" }
$runUrl = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
$text = "⏳ <b>Старт сборки релиза</b>`n<b>Репозиторий:</b> <code>${{ github.repository }}</code>`n<b>Тег:</b> <code>$target</code>`n<a href=`"$runUrl`">Открыть лог сборки</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text -DisableNotification
- name: Download latest LSLib release
run: |
$ErrorActionPreference = "Stop"
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/Norbyte/lslib/releases/latest"
$asset = $release.assets | Where-Object { $_.name -match '\.zip$' } | Select-Object -First 1
if (-not $asset) {
throw "Could not find a downloadable LSLib zip asset in the latest release."
}
Invoke-WebRequest -UseBasicParsing -Uri $asset.browser_download_url -OutFile ".tools/lslib/lslib.zip"
Expand-Archive -LiteralPath ".tools/lslib/lslib.zip" -DestinationPath ".tools/lslib" -Force
- name: Build .pak
run: |
$ErrorActionPreference = "Stop"
$divine = Get-ChildItem -Path ".tools\\lslib" -Recurse -File |
Where-Object { $_.Name -ieq "Divine.exe" } |
Select-Object -First 1
if (-not $divine) {
throw "Divine.exe was not found in the downloaded LSLib release."
}
& ".\\scripts\\build.ps1" -DivinePath $divine.FullName -Workspace "${{ github.workspace }}" -VersionTag "${{ steps.vars.outputs.version_tag }}"
- name: Show build result
run: |
$ErrorActionPreference = "Stop"
$archiveBaseName = "DnD 5.5e AIO Russian"
if ($env:GITHUB_REF -like "refs/tags/v*") {
$archiveBaseName = "DnD 5.5e AIO Russian $env:GITHUB_REF_NAME"
}
$zipPath = [System.IO.Path]::GetFullPath((Join-Path "${{ github.workspace }}" "build/$archiveBaseName.zip"))
Get-ChildItem "build/DnD 5.5e AIO Russian.pak", "build/info.json", $zipPath |
Select-Object FullName, Length, LastWriteTime
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: bg3-dnd55e-russian-localization-${{ github.run_number }}
path: build/*
if-no-files-found: error
- name: Create or update GitHub release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$tagName = "${{ github.ref_name }}"
$zipPath = Join-Path $env:GITHUB_WORKSPACE "build\\DnD 5.5e AIO Russian $tagName.zip"
$isPrerelease = "${{ steps.vars.outputs.is_prerelease }}" -eq "true"
if (-not (Test-Path -LiteralPath $zipPath)) {
throw "Release archive was not found at '$zipPath'."
}
gh release view $tagName --repo "${{ github.repository }}" *> $null
if ($LASTEXITCODE -ne 0) {
$args = @(
"release", "create", $tagName, $zipPath,
"--repo", "${{ github.repository }}",
"--title", $tagName,
"--notes", ""
)
if ($isPrerelease) {
$args += "--prerelease"
}
gh @args
}
else {
gh release upload $tagName $zipPath `
--repo "${{ github.repository }}" `
--clobber
$releaseId = gh release view $tagName `
--repo "${{ github.repository }}" `
--json id `
--jq ".id"
$releaseBodyPath = Join-Path $env:RUNNER_TEMP "release-body.json"
@{
name = $tagName
prerelease = $isPrerelease
} | ConvertTo-Json -Compress | Set-Content -LiteralPath $releaseBodyPath -Encoding utf8
gh api `
--method PATCH `
-H "Accept: application/vnd.github+json" `
"/repos/${{ github.repository }}/releases/$releaseId" `
--input $releaseBodyPath
}
- name: Notify Telegram about build success
if: success() && startsWith(github.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$releaseUrl = "${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }}"
$text = "🏁 <b>Релиз собран успешно</b>`n<b>Тег:</b> <code>${{ github.ref_name }}</code>`n<a href=`"$releaseUrl`">Открыть готовый релиз</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text -DisableNotification
- name: Notify Telegram about build failure
if: failure() && startsWith(github.ref, 'refs/tags/v')
continue-on-error: true
env:
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
$ErrorActionPreference = "Stop"
$runUrl = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
$text = "❌ <b>Сборка релиза завершилась ошибкой</b>`n<b>Тег:</b> <code>${{ github.ref_name }}</code>`n<a href=`"$runUrl`">Открыть лог сборки</a>"
.\scripts\send-telegram-notification.ps1 -BotToken $env:BOT_TOKEN -ChatId $env:TG_CHAT_ID -ThreadId $env:TG_THREAD_ID -Text $text

4
.gitignore vendored
View File

@@ -2,6 +2,10 @@ build/
build-stage*
.tools/
.cache/
.env
.env.local
.env.*
!.env.example
*.pak
*.tmp
*.temp

293
AGENTS.md
View File

@@ -1,91 +1,236 @@
# AGENTS.md
## General Rules (MUST)
## Execution Model (MUST)
- Read this file first; treat as system-level constraints.
- Priority:
1. User instructions
2. AGENTS.md
3. Existing code/style
4. Best practices
- Prefer minimal, non-breaking changes.
- Do not introduce unnecessary abstractions.
### 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.
---
### Cleanup (General)
- Do not leave temporary/debug artifacts in repo.
- Remove additional debug/temp dirs unless user asked to keep them.
## Communication (MUST)
- Answer first, then request approval if needed.
- Concise, meaningful, no filler.
- Do not end response with only procedural choice.
### 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.
Approval/clarification:
- ask once, no repetition
- binary → yes/no
- multiple → numbered options + brief context
### Communication (General)
- Project file links in user-facing Markdown: relative paths, `/` separators, spaces encoded as `%20`.
- File links in repo docs/checklists: relative paths, `/`, spaces as `%20`.
- In assistant UI responses, use the link format required by the execution environment; include relative path text when possible.
## Project-Specific Rules (MUST)
---
### 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.
## Git Workflow (MUST)
- Never commit/push without explicit user approval.
- After approval → commit + push immediately.
- Commit messages: Russian, factual (what was done).
- Branch (`fix/*` or `feat/*`): ask once before the first file-changing task that may lead to commit; reuse decision for all subsequent tasks in same dialogue.
### Canonical Paths
- Mod sources: `Mods/DnD 5.5e AIO Russian`
- 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`
- 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`
After work in `fix/*` or `feat/*`:
1. create PR/MR targeting `main`
2. merge changes into `main` and delete the source branch
### 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.
Push failure:
- retry ≤2 times, 3s delay
### Build/CI Contract
- CI workflow stays thin:
1. prepare workspace
Release link:
- provide `[version](url)` immediately after tag push, without waiting for CI
---
## Scope (MUST)
- Repo = Russian localization mod only.
Allowed:
- localization content
- packaging/release metadata
Forbidden:
- gameplay logic
- Script Extender
- unrelated assets
- Repo must remain source-only.
- Never commit `.pak` or build artifacts.
---
## Paths (MUST)
- Mod: `Mods/DnD 5.5e AIO Russian`
- Localization: `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
- Metadata: `Mods/DnD 5.5e AIO Russian/meta.lsx`
- Build: `scripts/build.ps1` _(single source of build truth)_
- CI: `.gitea/workflows/build.yml`
- Glossary: `glossary/glossary.normalized.json` _(primary terminology reference)_
- Actions: `ACTIONS.md`
- Local env template: `.env.example`
- Local env file: `.env.local`
- Upstream EN reference: `https://github.com/Yoonmoonsik/dnd55e/blob/main/Mods/DnD2024_897914ef-5c96-053c-44af-0be823f895fe/Localization/English/english.xml`
---
## Packaging (MUST)
- `.pak` contains ONLY `Mods/...`
Required:
- `meta.lsx`
- `russian.xml`
Forbidden in `.pak`:
- `.git`, `.gitea`
- `scripts`, `tools`, `.tools`
- `build`, staging dirs
Staging:
- use `%TEMP%`
- not inside repo
---
## Build & CI (MUST)
Flow:
1. prepare
2. download Divine
3. call `scripts/build.ps1`
4. publish tag archive
- Expected build outputs:
- `build/DnD 5.5e AIO Russian.pak`
3. run `scripts/build.ps1`
4. publish
Outputs:
- `build/*.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`.
- `build/*.zip` (tag only)
### 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`.
Release ZIP:
- only `.pak` + `info.json`
### 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`.
Triggers:
- automatic: push tag `v*`
- manual: workflow_dispatch
- branch pushes without tag MUST NOT publish release artifacts
### 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).
---
### Cleanup (Project-Specific)
- Ignored/temp patterns include: `build/`, `build-stage*`, `.tools/`, `*.pak`.
## Versioning (CRITICAL)
Source of truth:
`ModuleInfo/Version64` — read via explicit XML parsing:
`save/region[@id="Config"]/node[@id="root"]/children/node[@id="ModuleInfo"]/attribute[@id="Version64"]`
Rules:
- do not change `PublishVersion`
- tag MUST match version
- tag formats:
- stable: `vX.Y.Z` -> `Version64 = X.Y.Z.0`
- suffixed: `vX.Y.Z-suffix` -> `Version64 = X.Y.Z.N`
- for suffixed tags, suffix affects tag/release channel only and is NOT encoded in `Version64`
- for suffixed tags on the same base version `X.Y.Z`, increment `build` (`N`) by counting prior released tags `vX.Y.Z-*`; current release uses the next value starting from `1`
- stable tag without suffix always uses `build = 0`, even if suffixed releases for the same base version already existed
Before tag:
1. if version already changed → use it
2. if same as last → bump:
`scripts/set-version.ps1 -VersionTag <tag>`
`build.ps1`:
- derives version from tag
- writes to `info.json` + staged `meta.lsx`
Conflict resolution (MUST):
- before release, `Version64` in `meta.lsx` MUST equal target tag version
- if mismatch, run `scripts/set-version.ps1 -VersionTag <tag>` and re-check
- if still mismatch, release is blocked
---
## info.json (MUST)
Root:
- `Mods`, `MD5`
Per mod:
- Author, Name, Folder, Version
- Description, UUID, Created
- Dependencies (array), Group
Dependency UUID:
`897914ef-5c96-053c-44af-0be823f895fe`
---
## Guardrails (MUST)
Before commit:
- scope valid (localization/metadata only)
- no forbidden content
- no build artifacts (`.pak`, `build/`, staging)
- no temp/debug artifacts; ignored patterns MUST be present in `.gitignore`: `build/`, `build-stage*`, `.tools/`, `*.pak`
- packaging invariants intact
- version consistent (if applicable)
Before push:
- explicit user approval
- commit message valid (RU, factual)
Before release:
- version == tag
- version bumped if needed
- CI/build contract valid
- outputs correct (no extra files)
---
## Release & Changelog (MUST)
- Every release MUST include changelog.
Changelog:
- language: Russian
- concise, user-facing
- describe WHAT changed
- group logically
Sources:
- prefer diff over commits
Diff rules:
- inspect real file changes
Localization (`russian.xml`):
- added / changed / removed strings
- summarize user-visible impact (UI, spells, descriptions)
Metadata / CI:
- describe effect, not raw edits
Large diff:
- group + summarize
If no visible changes:
- state "техническое обновление"
Before release:
- generate changelog draft
- ask for approval
Approval gates:
- Gate A: explicit approval for commit/push (code/content changes)
- Gate B: explicit approval for release publish (after changelog draft)
Release message:
- version
- changelog
- `[version](url)` if derivable
Do not:
- invent changes
- include internal noise
---
## Rules Maintenance (MUST)
- Changes to `AGENTS.md` / `ACTIONS.md`: prefer compressed, machine-readable edits.
- Keep updates minimal and non-duplicative: merge overlapping points, remove redundancy, preserve intent.

View File

@@ -2023,9 +2023,7 @@
Пламя огня заклинаний. Вы изучаете заговор «Священное пламя». Также вы можете накладывать этот заговор бонусным действием количество раз, равное вашему бонусу мастерства, восстанавливая все потраченные использования после завершения долгого отдыха.</content>
<content contentuid="h502820d0g8b37g078bg65f7g4a80446379d8" version="1">Наёмник Жентарима</content>
<content contentuid="hdeab958dge913gf95bg123dg27338a365d6d" version="2">Черта: Громилa Жентарима&lt;br&gt;&lt;br&gt;Возможно, вам нужны были деньги. Возможно, вы жаждали найти семью, какой бы сомнительной она ни была. Или, может, вы просто умеете добиваться своего любыми средствами. Какой бы ни была ваша причина, вы вступили в ряды Жентарима — самой одиозной наёмнической гильдии в Царствах. Хотя лидеры Жентарима настаивают, что их организация больше похожа на семью, чем на теневой синдикат, мало какие семьи демонстрируют столько же обмана, кумовства и коррупции. Вы отточили свою хитрость, реакцию и владение клинком, чтобы подняться по иерархии гильдии.&lt;br&gt;&lt;br&gt;Пользоваться моментом. Когда вы бросаете урон от Атаки возможности, вы можете бросить кости урона дважды и выбрать любой результат против цели.
Семья прежде всего. Один раз за Долгий отдых вы даруете себе и союзникам в пределах 30 футов от вас преимущество на броски инициативы.</content>
<content contentuid="hdeab958dge913gf95bg123dg27338a365d6d" version="2">Черта: Громила Жентарима&lt;br&gt;&lt;br&gt;Возможно, вам нужны были деньги. Возможно, вы жаждали найти семью, какой бы сомнительной она ни была. Или, может, вы просто умеете добиваться своего любыми средствами. Какой бы ни была ваша причина, вы вступили в ряды Жентарима — самой одиозной наёмнической гильдии в Царствах. Хотя лидеры Жентарима настаивают, что их организация больше похожа на семью, чем на теневой синдикат, мало какие семьи демонстрируют столько же обмана, кумовства и коррупции. Вы отточили свою хитрость, реакцию и владение клинком, чтобы подняться по иерархии гильдии.&lt;br&gt;&lt;br&gt;Пользоваться моментом. Когда вы бросаете урон от Атаки возможности, вы можете бросить кости урона дважды и выбрать любой результат против цели.&lt;br&gt;&lt;br&gt;Семья прежде всего. Один раз за Долгий отдых вы даруете себе и союзникам в пределах 30 футов от вас преимущество на броски инициативы.</content>
<content contentuid="h6c0d30c6gd6bagd6f9g6ed0g7d30fcd174dd" version="1">Черта: Ледяной чародей</content>
<content contentuid="he5401f43ga2cfgcd9dgcb25g6ce8548e686a" version="1">Заговор. Вы изучаете заговор «Луч холода».
@@ -2130,19 +2128,19 @@
<content contentuid="h8bfddb10g014ag1ffagebccgfc7f04dceadc" version="1">Смертоносная стрельба</content>
<content contentuid="h76568432gfb37gc67cgcb8egf3cebfdfa094" version="1">Бонусным действием вы получаете Преимущество на проверку атаки из дальнобойного оружия в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этом ходу, а после его использования ваша скорость становится 0 до конца хода.</content>
<content contentuid="h93af975eg1568g7912g8166g2000a25d6098" version="1">Отблеск Гнева</content>
<content contentuid="h89b6e006g139bgf893g9a7dg642ea772fd27" version="2">Бонусным действием вы можете заставить wieldемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.</content>
<content contentuid="h89b6e006g139bgf893g9a7dg642ea772fd27" version="2">Бонусным действием вы можете заставить удерживаемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.</content>
<content contentuid="hcd7ebaacgd104g7c94g39abga1726e706521" version="1">Отблеск Гнева</content>
<content contentuid="h44cd6db6gdaa7gaac2g514cg11ae3a98f828" version="1">Бонусным действием вы можете заставить wieldемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.</content>
<content contentuid="h44cd6db6gdaa7gaac2g514cg11ae3a98f828" version="1">Бонусным действием вы можете заставить удерживаемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.</content>
<content contentuid="hec257629g3869gaf27g7d40g1a0a389564d4" version="1">Ролевая игра «Властелин Колец»</content>
<content contentuid="h5660c039gdc35g3a65gfe67gdd812981ab72" version="1">Ролевая игра «Властелин Колец»</content>
<content contentuid="h8e50ea07g829fg41c1gfdaeg0618e6beca34" version="1">Смертоносная стрельба</content>
<content contentuid="h0f8ca247g66e5g6260ga870g3e352f39fa6e" version="2">Эльфы обладают врождённым талантом к точной стрельбе из лука. Вы отточили этот дар практически до совершенства, и ваши стрелы находят цель с поразительной точностью.&lt;br&gt;&lt;br&gt;Бонусным действием вы можете даровать себе преимущество на бросок атаки дистанционным оружием в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этот ход, и после его использования ваша скорость становится 0 до конца текущего хода.</content>
<content contentuid="h6e348a1aga0b0g9788gb85dgeba21fa79668" version="1">Драконоборец</content>
<content contentuid="ha77cb967ga90dgf3bagbb92g9b2af176e030" version="2">Легенда о Барде Лучнике вдохновила многих юношей и девушек из Долины, заставив их жаждать доказать свою ценность убийством великого чудовища. Как и многие до вас, вы долго размышляли над способами борьбы с существами крупных размеров, надеясь однажды снискать славу, победив их.&lt;br&gt;&lt;brВы совершаете броски атаки с преимуществом против крупных и более крупных существ. Кроме того, при попадании по таким существам вы наносите дополнительный урон, равный вашему модификатору Силы.</content>
<content contentuid="ha77cb967ga90dgf3bagbb92g9b2af176e030" version="2">Легенда о Барде-Лучнике вдохновила многих юношей и девушек из Долины, заставив их жаждать доказать свою ценность убийством великого чудовища. Как и многие до вас, вы долго размышляли над способами борьбы с существами крупных размеров, надеясь однажды снискать славу, победив их.&lt;br&gt;&lt;br&gt;Вы совершаете броски атаки с преимуществом против крупных и более крупных существ. Кроме того, при попадании по таким существам вы наносите дополнительный урон, равный вашему модификатору Силы.</content>
<content contentuid="h27541f87g50d6gf441gbab6geae246f77643" version="1">Мощный выстрел</content>
<content contentuid="ha574dfdfgf055g69c1g9889gce2fffa674e6" version="2">Хотя Чёрная стрела, сразившая дракона Смауга, возможно, была предназначена для этого судьбой, рука, запустившая её с такой силой, была невероятно крепка. Когда вы мечете копье или натягиваете лук, вы следите за тем, чтобы ваш хват был твёрдым, а прицел верным.&lt;br&gt;&lt;br&gt;Совершая атаку дистанционным оружием, вы используете модификатор Силы для бросков атаки и урона. Вы должны использовать один и тот же модификатор для обоих бросков.</content>
<content contentuid="h2ffcb591g0cc8ga4f6g50f8g0b3e5ae7dcf0" version="1">Отблеск Гнева</content>
<content contentuid="h63f9716cgc2d1g6fcbg46fcg4c5e77c4be1d" version="2">Ваш народ видел множество поражений и множество бесплодных побед в войнах против Тени. Смертоносная ярость, которую ваши сородичи питают к Врагу, наполняет ваше оружие отблеском холодного пламени.&lt;br&gt;&lt;br&gt;Бонусным действием вы можете заставить wieldемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.&lt;br&gt;&lt;br&gt;При попадании критический удар атакой оружия ближнего боя, вы можете бросить одну из костей урона этого оружия дополнительным раз и добавить результат.</content>
<content contentuid="h63f9716cgc2d1g6fcbg46fcg4c5e77c4be1d" version="2">Ваш народ видел множество поражений и множество бесплодных побед в войнах против Тени. Смертоносная ярость, которую ваши сородичи питают к Врагу, наполняет ваше оружие отблеском холодного пламени.&lt;br&gt;&lt;br&gt;Бонусным действием вы можете заставить удерживаемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.&lt;br&gt;&lt;br&gt;При критическом попадании атакой оружием ближнего боя вы можете ещё раз бросить один из кубиков урона этого оружия и добавить результат.</content>
<content contentuid="hbae36f77gae16gae82g11feg104123766490" version="1">Проворство</content>
<content contentuid="h624efb18g8179g6ce0gbe0fg03d310683b76" version="2">Ваше мастерство (или везение?) в бою растёт вместе с набранным опытом.&lt;br&gt;&lt;br&gt;Вы получаете бонус +1 к КБ. Вы теряете этот бонус, если находитесь в состоянии недееспособности или держите щит.</content>
<content contentuid="hc2022d67g4441g1ca0g979ag85847dde7f08" version="2">Раса: Аасимар</content>
@@ -2589,11 +2587,7 @@
<content contentuid="h3b7d345ag5345g49ffg6c00g90a138a19f95" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений &lt;LSTag Tooltip="Intelligence"&gt;Интеллекта&lt;/LSTag&gt;, &lt;LSTag Tooltip="Wisdom"&gt;Мудрости&lt;/LSTag&gt; и &lt;LSTag Tooltip="Charisma"&gt;Харизмы&lt;/LSTag&gt;.</content>
<content contentuid="h3d2a0062g8a04g4942g776fg8389d4b68f1b" version="1">Примите облик белого медведя, который может &lt;LSTag Type="Spell" Tooltip="Shout_GoadingRoar_Bear_Summon"&gt;Провоцировать&lt;/LSTag&gt; врагов атаковать его.</content>
<content contentuid="h4afbde24g3b4ag6245gdfc5gf40fbb6dd7b9" version="1">Уровень 3: Бах! И ты труп!</content>
<content contentuid="h4b3edb5dg700eg11d2gb46eg867808ec1738" version="2">Ваш опыт борьбы с заклинателями даёт вам следующие преимущества.&lt;br&gt;&lt;br&gt;Разрушитель концентрации. Каждый раз, когда вы совершаете дальнобойный бросок атаки, вы нарушаете защитную магию, действующую на цель, и прерываете её концентрацию.
Антимагический выстрел. Когда вы совершаете критическое попадание по цели под действием вашей особенности «Выстрел в живот», вы также подавляете её способность накладывать заклинания. Пока снаряд остаётся в цели, она не может накладывать заклинания или выполнять действие «Магия».
Закалённый магией. Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить результат к броску, потенциально превращая провал в успех.</content>
<content contentuid="h4b3edb5dg700eg11d2gb46eg867808ec1738" version="2">Ваш опыт борьбы с заклинателями даёт вам следующие преимущества.&lt;br&gt;&lt;br&gt;Разрушитель защитной магии. Каждый раз, когда вы совершаете дальнобойный бросок атаки, вы временно нарушаете защитную магию, действующую на цель. На время этой атаки эффекты заклинаний, нацеленных на существо, такие как «Доспех мага», а также свойства и силы магических предметов, которые существо носит или держит, подавляются и не действуют. Цель этой атаки не может Реакцией накладывать заклинания вроде «Щита» в ответ на атаку или её урон.&lt;br&gt;&lt;br&gt;Антимагический выстрел. Когда вы совершаете критическое попадание по цели под действием вашей особенности «Выстрел в живот», вы также подавляете её способность накладывать заклинания. Пока снаряд остаётся в цели, она не может накладывать заклинания или выполнять действие «Магия».&lt;br&gt;&lt;br&gt;Закалённый магией. Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить результат к броску, потенциально превращая провал в успех.</content>
<content contentuid="h5ef73b58ga233g8fccg4389gcd443dd5c7d7" version="1">Закалённый магией</content>
<content contentuid="h672e82feg2734g7895ga227gd76ffbfded41" version="1">Примите облик гигантского барсука, который может &lt;LSTag Type="Spell" Tooltip="Target_Burrow_GiantBadger"&gt;Зарываться&lt;/LSTag&gt; в землю.</content>
<content contentuid="h6827c922gd910gca98gee99gdbd9b0f178b1" version="1">Примите облик глубинного ротэ, который может накладывать &lt;LSTag Type="Spell" Tooltip="Target_DancingLights"&gt;Пляшущие огоньки&lt;/LSTag&gt; и &lt;LSTag Type="Spell" Tooltip="Rush_Rush_DeepRothe"&gt;Таранить&lt;/LSTag&gt; врагов.</content>
@@ -2635,4 +2629,126 @@
<content contentuid="hc1e53854g0170gb211geca0geb2971202e92" version="1">Когда видимый вами союзник в пределах 60 футов получает попадание атакой, вы можете Реакцией потратить одну Кость риска, чтобы совершить атаку дальнобойным оружием по атакующему. При использовании союзник получает Временные очки здоровья, равные выпавшему значению Кости риска.</content>
<content contentuid="hcf85f5ddg214egc39bga0e8g7ee910e3ffec" version="1">Уровень 3: Призвать к порядку</content>
<content contentuid="hd43fb4c9g35d7g9ab6g9cc2g37fbd18e9590" version="1">Когда вы совершаете критическое попадание по существу, вы можете потребовать, чтобы цель сдалась. Цель должна преуспеть в спасброске Мудрости против СЛ спасброска ваших манёвров, иначе получит состояние «Испуган» на 1 минуту. Существо может повторять этот спасбросок Мудрости в конце каждого своего хода, оканчивая это состояние при успехе.</content>
<content contentuid="h00467a34g3b88g453cg081eg6e394afa8e61" version="1">Кровавые рыцари Ада служат Сутеху, Владыке Крови. Их чары высасывают жизненную силу врагов, обращая её в инфернальные ритуалы, способные переломить ход битвы.&lt;br&gt;&lt;br&gt;Сутех правит Наракой, Городом Крови. Признанный величайшим чародеем Ада, он носит титул Верховного сангвинария и властвует из Храма Жизненной силы. Он - мастер магии крови, а его ближайший круг жрецов и волшебников составляют кровеличи, неживые заклинатели, чьи телесные формы обратились в прах столетия назад и чьи тела сотворены из цельной крови.&lt;br&gt;&lt;br&gt;Все иллриггеры Сутеха принадлежат культу, известному как Чаша Жизненной силы. Рыцари Чаши жадно пьют сущность своих врагов, истощая её, чтобы питать свою магию. Другие члены Ордена Осквернения опасаются, что Кровавые рыцари стремятся не только возвести Сутеха на Трон Ада; некоторые шепчут, что Чаша втайне замышляет сделать Сутеха богом. Разумеется, это было бы изменой.</content>
<content contentuid="h018dfa63g9034g560eg6daegcc0414738e0e" version="1">Цена крови: к12</content>
<content contentuid="h0294b05egc63bg881age868g1c91eaec6dab" version="1">Печать</content>
<content contentuid="h08b904f9gd5aeg7159g0bb5g4f1a264718cc" version="1">Уровень 3: Воодушевление союзников</content>
<content contentuid="h09168b32g9fe8gac39gedf6g93f7c55f5e17" version="1">Наложите на цель одну печать иллриггера.</content>
<content contentuid="h0f8708dbgeff3g4172g8db1g4d944084b8c1" version="1">Когда существо, которое вы видите, наносит урон вам или союзнику в пределах 30 футов от вас, вы можете реакцией потратить печать, чтобы уменьшить получаемый целью урон на 1d10 + половину вашего уровня иллриггера, округлённую вниз.</content>
<content contentuid="h12ae9a64g3bfcg9c68gbc56g02b570979b53" version="1">Это существо осуждено силой Ада.</content>
<content contentuid="h158d6a37ga6c0ga147gbd62g2f7886d1d51b" version="1">Получите бонус +2 к КБ на 1 минуту.</content>
<content contentuid="h16ca83e4gd87cge240g4682g2480a4a6ba19" version="1">Когда Сутех принимает вас как своего иллриггера, он дарует вам доступ к своему кощунственному владычеству над кровью и жизнью. Вы получаете мастерство в навыке «Религия».</content>
<content contentuid="h16cf04ffg2962g897ag364fgea809b7c8c16" version="2">Уровень 2: Апатия Стикса</content>
<content contentuid="h173e97b1g24c4gd71cg2b14gc7e43d9e037a" version="2">Уровень 2: Гибкость</content>
<content contentuid="h1ad18073g65adg793ege0dbg830abdc2841d" version="1">Осуждён</content>
<content contentuid="h1e93a04fgfcb3ge71cg8edcg9d735d49cff7" version="1">Бонусным действием вы восстанавливаете количество очков здоровья, равное пятикратному уровню вашего иллриггера, распределяя это исцеление по своему выбору между собой и другими существами в пределах 30 футов от вас.</content>
<content contentuid="h1f95a7f0g7d0fgabfagfa1cg5cbc0b67b2bb" version="1">Покров теней</content>
<content contentuid="h2702a54eg25d6geee6g89b1g70955d610f89" version="1">Вы можете бонусным действием потратить печать, чтобы окутать себя или существо, которого касаетесь, мантией из полутеней. Цель получает бонус +2 к КБ на 1 минуту.</content>
<content contentuid="h283a80a7gdc57ge48fg2323g0a19321fd070" version="2">Требуется 7-й уровень иллриггера или выше.&lt;br&gt;&lt;br&gt;Вы можете бонусным действием потратить печать, чтобы телепортироваться на расстояние до 30 футов в незанятое пространство, которое видите.</content>
<content contentuid="h29248f55g9799g09d8ge529g06704f76d155" version="1">Уровень 10: Цена крови</content>
<content contentuid="h2ab85095gd8e1gd4f8g159bg5479bf29202f" version="1">Осуждение</content>
<content contentuid="h3193c1b1g81cegd32cg4ceag8e352d5d18eb" version="1">Воодушевление союзников</content>
<content contentuid="h331a5a4cg366cg82a3g63e0g6899f199c8bb" version="1">Бонусным действием вы восстанавливаете количество очков здоровья, равное пятикратному уровню вашего иллриггера, распределяя это исцеление по своему выбору между собой и другими существами в пределах 30 футов от вас.</content>
<content contentuid="h3518e2eeg9593gae20g3e88gff65f6d3e6bb" version="1">Ослабляющая печать</content>
<content contentuid="h357af6fegcc60g52adgea2agd98b8ed57793" version="1">Цена крови: к6</content>
<content contentuid="h36291e60ge6ddg0237g008dgbfdf54c658c5" version="1">Кровь за кровь</content>
<content contentuid="h368ac3efg0988g4f4ag0fa9g6819e55d1577" version="1">Высвобождение ада: Огонь</content>
<content contentuid="h4087e43dg1626g7141gb492g08612b1d6f08" version="4">Уровень 7: Огненный канал</content>
<content contentuid="h4455cd21gb4e4ga171g4a01gfd22b925fb9b" version="2">Когда существо совершает дальнобойную атаку по вам или по союзнику, которого вы видите в пределах 30 футов от вас, вы можете реакцией потратить печать и совершить дальнобойную атаку оружием по атакующему. Если атака попадёт, она нанесёт дополнительный урон, равный половине вашего уровня иллриггера, округлённой вниз.</content>
<content contentuid="h450bf76dg7e82gc1f2g03a3g2f6003609ae8" version="1">Каждый раз, когда союзник получает урон от осуждённого существа, это осуждённое существо получает некротический урон, равный вашему бонусу мастерства.</content>
<content contentuid="h4645bf49gb7f4g564bgeb3fgfb357d560904" version="1">Боевое мастерство: Ложь</content>
<content contentuid="h467e4d93g2c88g87degd375gc54b5a65a88d" version="1">Пожирание</content>
<content contentuid="h4b36c8d5g7a7fgf9degbde8ga2790584725d" version="1">Наложить печать</content>
<content contentuid="h4c1994b8gf35cg365bg459eg53670e2a390f" version="2">Уровень 2: Нескованный</content>
<content contentuid="h4c1c236cg2298g059bgbffbg251a8f869bb7" version="1">Ваши атаки наполнены сокрушительной мощью. Когда вы попадаете атакой оружием, вы наносите дополнительный 1d8 урона выбранного типа.</content>
<content contentuid="h51e32293ge4aagbb61g14d2g23c2e8863236" version="1">Уровень 7: Кровь за кровь</content>
<content contentuid="h565e4f77gf679gd18fgcb9bg38afc492e620" version="2">Уровень 2: Порча</content>
<content contentuid="h58aa4956gcc27g9d75g019cgd0340e796f47" version="2">Уровень 2: Бравада</content>
<content contentuid="h5b9a73a3g7b6fg13b5gc7c3gdf58090d1c36" version="1">Выстрел возмездия</content>
<content contentuid="h5ead3298gb213gac53g0d3egaf553c018315" version="4">Уровень 7: Покров теней</content>
<content contentuid="h5f2491abg74f5g071bgce31ge29ac76995fd" version="1">Ваш архидьявол наделяет вас сверхъестественным мастерством в определённом стиле боя. Выберите одно из следующих боевых мастерств иллриггера:</content>
<content contentuid="h608611fbg8b32g738cg8750g599765b12de7" version="1">Уровень 11: Кровавый удар</content>
<content contentuid="h6266397ag443dg94a5g8c87g09accc415506" version="1">Уровень 3: Обескровливание</content>
<content contentuid="h6648aa69gf7b4gfd26gf073gf24f1802047b" version="1">Кровавый удар</content>
<content contentuid="h67853750g16c0g9b13gc3efgbaba43af6259" version="2">Уровень 2: Ослабляющая печать</content>
<content contentuid="h67c6cf13g3c76g42a4gfac4g3b887eeb9730" version="1">Когда вы попадаете по существу атакой оружием ближнего боя, вы можете переместиться, не провоцируя атак по возможности.</content>
<content contentuid="h6975a894g14d4g0b4dga8ecg80a20b85ad85" version="1">Вы можете укреплять свою защиту ценой жизненной силы. Когда вы проваливаете спасбросок, вы можете потратить одну из своих Костей хитов, бросить её и добавить выпавшее значение к результату спасброска.</content>
<content contentuid="h69a470cbg07f2g82ddg9603g8091c23a6a5a" version="1">Когда вы используете своё «Зловещее осуждение», чтобы наложить или сжечь печать, дальность этой способности составляет 60 футов вместо 30. Когда на 6-м уровне вы получаете особенность «Инфернальный проводник», её дальность становится 30 футов вместо касания.&lt;br&gt;&lt;br&gt;Кроме того, совершение дальнобойной атаки, пока враждебное существо находится в пределах 5 футов от вас, не накладывает помеху на бросок атаки.</content>
<content contentuid="h6b7c5b42gc865g9a66g25bcgcf6498b47b95" version="1">Прилив сил</content>
<content contentuid="h6bdae4c0g3fcdg259fgefa5g636614568bc0" version="1">Вы можете укреплять свою защиту ценой жизненной силы. Когда вы проваливаете спасбросок, вы можете потратить одну из своих Костей хитов, бросить её и добавить выпавшее значение к результату спасброска.</content>
<content contentuid="h6c2e1510g127eg4a2eg3cbfg9e940ea8f6e8" version="2">Когда вы сжигаете одну или несколько печатей на осуждённом существе, вы можете реакцией высвободить вокруг него взрыв адской энергии. Каждое выбранное вами существо в пределах 10 футов от цели должно совершить спасбросок Ловкости. При провале существо получает урон того же типа и в том же количестве, что нанесли печати осуждённому существу. При успехе существо получает половину этого урона.</content>
<content contentuid="h6db51716g1e60ga51dg64d5g8dce1924d495" version="2">Уровень 2: Неумолимость</content>
<content contentuid="h70a4510cgf224gfccfg83dbge051b2c47f34" version="2">Уровень 2: Пожиратель душ</content>
<content contentuid="h7403d9e2gd4ffg8c95g6874gba9be784c5c0" version="1">Кровавый рыцарь</content>
<content contentuid="h76541450ge22fg5ec9g80fag5c97af69f959" version="1">Кровавый удар</content>
<content contentuid="h79d9540bg9852g5efdg3c46g65c5ac3dee8d" version="1">Уровень 6: Инфернальный проводник</content>
<content contentuid="h7aa1b008g6ca0g4a20g5979g21ad1d5571fd" version="1">Кровь за кровь</content>
<content contentuid="h7e6eebc1g195fg638eg046cg1602e8341a8b" version="1">Цепь Ахерона</content>
<content contentuid="h8024deb6g1171ge522g2119g5c416be99cb1" version="2">Вы можете вытягивать силы из врагов, воодушевляя союзников. Когда вы сжигаете одну или несколько печатей на существе, вы можете выбрать союзника, которого видите в пределах 30 футов от вас. Этот союзник получает временные очки здоровья, равные урону, который печати нанесли осуждённому существу.</content>
<content contentuid="h83e42a83g88c6gd1e9g8c16gbff3333ac045" version="1">Апатия Стикса</content>
<content contentuid="h844db55cg4f62gabe3gbb9cg8faa13c91a88" version="1">Вы можете бонусным действием потратить печать, чтобы телепортироваться в незанятое пространство, которое видите.</content>
<content contentuid="h84a9d67fge555g185dg5c83g4c97364a1cef" version="1">Пожиратель душ</content>
<content contentuid="h8942f538g5702g7f64g961agb06622c01a69" version="1">Уровень 11: Ужасающая сила (Огонь)</content>
<content contentuid="h8993f01fgfd8fg8b11g17cdg458c682aad00" version="3">Требуется 7-й уровень иллриггера или выше.&lt;br&gt;&lt;br&gt;Когда вы бонусным действием накладываете или перемещаете печать на существо размера «Большой» или меньше, вы можете активировать этот дар, не тратя действия. Вы призываете инфернальные цепи, которые хватают цель и заставляют её совершить спасбросок Силы. При провале вы либо притягиваете существо на 10 футов к себе, либо оно оказывается схваченным до конца вашего следующего хода.</content>
<content contentuid="h89f9d62cgd6f0g46b6g9bdbgeb45a121b113" version="1">Цена крови: к10</content>
<content contentuid="h8a78754bge4a1gbe50g8f26g0bd670acd7cc" version="1">Цена крови: к8</content>
<content contentuid="h8b1d72d3gf86ag8352gbc15ga4b6db6acf02" version="1">Огненный канал</content>
<content contentuid="h8b8b45b5g6648g381bg1bccg8ba31a12abd5" version="1">Вы можете вытягивать силы из врагов, воодушевляя союзников. Когда вы сжигаете одну или несколько печатей на существе, вы можете выбрать союзника, которого видите в пределах 30 футов от вас. Этот союзник получает временные очки здоровья, равные урону, который печати нанесли осуждённому существу.</content>
<content contentuid="h8cd365c4g7106g7c48g3af0gaa9b0e85108b" version="1">Получите Временные очки здоровья, равные вашему уровню иллриггера.</content>
<content contentuid="h8fd47ec3gac34g4144g6243g234bbbbbb9ae" version="1">Цель должна совершить спасбросок Телосложения. При провале она получает 3d10 некротического урона, а вы восстанавливаете очки здоровья, равные нанесённому урону. При успехе цель получает половину этого урона, а вы восстанавливаете очки здоровья, равные нанесённому урону.</content>
<content contentuid="h95248f4cg57e8g073dgc8a1gfbf3d472edee" version="1">Восстановить печати</content>
<content contentuid="h99d927b5gafddg8654g1453g82bbe0c6e89f" version="3">Иллриггер</content>
<content contentuid="h9b53ce98gbf9cg2509g1a23g24549bd6fad7" version="1">Уровень 1: Зловещее осуждение</content>
<content contentuid="ha334fba5ge039gfb31g1b15g55b8f07d80f4" version="1">Пожертвуйте половиной оставшихся &lt;LSTag Tooltip="HitPoints"&gt;очков здоровья&lt;/LSTag&gt;, чтобы исцелить цель на то же количество.</content>
<content contentuid="ha3823257g1d27g9d0egeff1gb7b607bf03f2" version="1">Воодушевление союзников</content>
<content contentuid="ha474cedcg9588gc4b6g1526gc012f1de7f42" version="1">Магия, защищающая ваших союзников, теперь также истощает силы их врагов. Когда союзник, у которого есть временные очки здоровья от вашей особенности «Обескровливание», получает попадание атакой ближнего боя, атакующий получает 11 некротического урона.</content>
<content contentuid="ha681b28ag70a2g67d8g9448g35565a4a34e5" version="1">Сжигание печатей: Огонь</content>
<content contentuid="ha7401e8dg37d9g7b31g97c4gc53803a8288b" version="1">Уровень 11: Ужасающая сила (Яд)</content>
<content contentuid="ha7e9ac57gfaedg252egecb1g6f787805f11a" version="2">Магия, защищающая ваших союзников, теперь также истощает силы их врагов. Когда союзник, у которого есть временные очки здоровья от вашей особенности «Обескровливание», получает попадание атакой ближнего боя, атакующий получает 11 некротического урона.</content>
<content contentuid="ha828937bg7769gb132g7f34g228ed13a6541" version="1">Сжигание печатей: Некротическая энергия</content>
<content contentuid="ha8eaee0fg754dg1d66g293cgf44cc84312b0" version="1">Вы получаете способность осуждать существ силой Ада. Один раз за ход вы можете наложить магическую печать на существо в пределах 30 футов от вас. Вы можете либо наложить эту печать, когда попадаете по цели атакой оружием, не тратя действия, либо бонусным действием наложить её на цель, которую видите в пределах дальности. Эта печать сохраняется, пока не будет сожжена. Существо, на котором есть одна или несколько ваших печатей, считается осуждённым.&lt;br&gt;&lt;br&gt;До отдыха вы можете наложить лишь ограниченное число печатей, а все потраченные печати восстанавливаются после короткого или длительного отдыха. На 1-м уровне вы можете иметь максимум три печати, на 3-м уровне - четыре, а на 7-м уровне - пять.&lt;br&gt;&lt;br&gt;Если осуждённое существо умирает, вы можете бонусным действием в свой ход переместить все наложенные на него печати на новое существо, которое видите в пределах 30 футов от него.</content>
<content contentuid="ha9df2c62g25e2g2614g66dagfe3cb0e26aa5" version="2">Требуется 7-й уровень иллриггера или выше.&lt;br&gt;&lt;br&gt;Вы можете бонусным действием потратить печать, чтобы окутать себя или существо, которого касаетесь, мантией из полутеней. Цель получает бонус +2 к КБ на 1 минуту.</content>
<content contentuid="haa0a6c9agc413g822cg52f2g69c530bc79d2" version="2">Не может совершать бонусные действия или реакции.</content>
<content contentuid="hacf9fc8fg665eg2cf7g70aaga3d8cc697843" version="2">Вы можете вытягивать силы из врагов, воодушевляя союзников. Когда вы сжигаете одну или несколько печатей на существе, вы можете выбрать союзника, которого видите в пределах 30 футов от вас. Этот союзник получает временные очки здоровья, равные урону, который печати нанесли осуждённому существу.</content>
<content contentuid="had6ccb0dg99e0ge0b3gfe64g2cc75c0d28a0" version="1">Высвобождение ада: Огонь</content>
<content contentuid="hb58aa744g9557g7770g99f4g200999b88429" version="2">Уровень 2: Свирепость</content>
<content contentuid="hb5dc95fdga8fdga177gedf9g3d1def6b18b4" version="1">Обескровливание</content>
<content contentuid="hb80a53b8gbab4ge493ge6fcg80ca85f5cccd" version="1">Пока вы не носите доспехи, ваш Класс доспеха равен 10 + модификатор Ловкости + модификатор Харизмы. Вы можете использовать щит и всё равно получать эту выгоду.</content>
<content contentuid="hb8cf1d46g6834g9baagbf6dg50299be625eb" version="1">Боевое мастерство</content>
<content contentuid="hbc394c87g819ag60dcgd3e4g840e740aede3" version="1">Вы можете усиливать союзников ценой самого себя или вытягивать жизненную силу врагов себе на пользу.</content>
<content contentuid="hc00e6ea9gfd5egcff2g4900g073fd4c5bd3f" version="2">Вы сжигаете все печати, наложенные вами на существо, нанося ему по 1d6 урона огнём или некротической энергией за каждую сожжённую печать, по вашему выбору. Сожжённая печать немедленно исчезает.&lt;br&gt;&lt;br&gt;Достигнув 5-го уровня в этом классе, вы укрепляете связь со своим архидьяволом. Каждая сожжённая печать наносит дополнительный 1d6 урона, всего 2d6 за печать. На 10-м уровне урон каждой печати увеличивается ещё на 1d6, всего до 3d6 за печать.</content>
<content contentuid="hc31d5eb8g73cdgf87bg7969geb0fc56b1687" version="1">Кровавые рыцари приносят клятву Сутеху, вступая в Орден Опустошения. Эти заповеди обязывают их владеть кощунственной магией крови, внушать верность и сеять ужас.&lt;br&gt;&lt;br&gt;Их сила - их слабость. Я поражаю сильнейших из врагов, ибо их жизненная сила питает мою победу.&lt;br&gt;&lt;br&gt;Грех требует страданий. Противостоять мне - ересь. Прежде чем мои враги вкусят поражение, они должны заплатить за своё неверие агонией.&lt;br&gt;&lt;br&gt;Верность вознаграждается. Мои дары заставляют союзников зависеть от меня и от кровопролития, которое меня усиливает.&lt;br&gt;&lt;br&gt;Милосердие - это сила. Даруя помощь союзникам, я доказываю, сколь велика моя мощь. Каждый раз, когда я возвращаю жизнь, это напоминает, как быстро я могу её отнять.</content>
<content contentuid="hc31eef37gcba0g3dd5g3558g3b4bd0528103" version="1">Вы получаете способность осуждать существ силой Ада.</content>
<content contentuid="hc323cc9cg84e2g474bg5925g8922b79a3ba5" version="1">Цель должна вычесть из результата спасброска, который совершит до конца своего следующего хода, число, равное бонусу мастерства иллриггера.</content>
<content contentuid="hc60731eeg14f1gbf2egea48g01b6d1ad45e8" version="1">Когда вы сжигаете печать на осуждённом существе, вы можете активировать этот дар, не тратя действия. Цель должна вычесть из результата спасброска, который совершит до конца своего следующего хода, число, равное вашему бонусу мастерства.</content>
<content contentuid="hc90683f4gd5c1g16a2g970bg5f9a7ef90301" version="1">Получает Временные очки здоровья.</content>
<content contentuid="hc952c432g8c2cgbbacg18f8gfc587dff1d4d" version="1">Когда существо совершает дальнобойную атаку по вам или по союзнику, которого вы видите в пределах 30 футов от вас, вы можете реакцией потратить печать и совершить дальнобойную атаку оружием по атакующему. Если атака попадёт, она нанесёт дополнительный урон, равный половине вашего уровня иллриггера, округлённой вниз.</content>
<content contentuid="hcdb79176g44f6gae88g5df2g09e6221e8525" version="1">Когда вы сжигаете печать на осуждённом существе, вы можете активировать этот дар, не тратя действия, и получить Временные очки здоровья, равные вашему уровню иллриггера.</content>
<content contentuid="hcef63acbg72bag27f7g9fadgf2d9d61c668d" version="1">Уровень 11: Ужасающая сила (Некротическая энергия)</content>
<content contentuid="hd7f9a6ddg5b45gf8acg0d89g98b6c796d969" version="1">При совершении атак и бросков урона оружием ближнего боя вы используете модификатор Харизмы вместо модификатора Силы или Ловкости.</content>
<content contentuid="hd8fa84e9gb7b9g487bgeb2agb80c8a9a3f17" version="1">Заповеди крови</content>
<content contentuid="hd92480ccg3244g6d8ag80d0g572cbd5b4da3" version="1">Каждый раз, когда союзник получает урон от осуждённого существа, это осуждённое существо получает некротический урон, равный вашему бонусу мастерства.</content>
<content contentuid="hd9f88f32g2a3eg5ee1gdb45g1b062c876f03" version="1">Архидьяволы, правящие Семью Городами Ада, бесконечно плетут козни. Каждый из них вечно замышляет подчинить остальных, взойти на Трон Ада, объединить Семь Городов и всех живущих там инфернальных существ, а затем повести неисчерпаемую армию дьяволов через времена и миры, пока не запылают все вселенные.&lt;br&gt;&lt;br&gt;Элитные оперативники этих архидьяволов - иллриггеры. Рыцари, убийцы, маги и террористические коммандос Ада, иллриггеры управляют полем боя, разрушают вражеские союзы и исполняют инфернальную волю своего архидьявола.</content>
<content contentuid="hdad51f43g5e81g7f25g4fa0gb0809a43973d" version="1">Высвобождение ада: Некротическая энергия</content>
<content contentuid="hdc3b3befg7002g12f7g8757g9c5c9409d33c" version="1">При совершении атак и бросков урона оружием ближнего боя вы используете модификатор Харизмы вместо модификатора Силы или Ловкости.</content>
<content contentuid="hdcff639cge97ag87c0gca5dgb607adff562c" version="1">Выстрел возмездия</content>
<content contentuid="hddb45062ge037g28d1g354fgec018a665978" version="1">Когда существо, которое вы видите, наносит урон вам или союзнику в пределах 30 футов от вас, вы можете реакцией потратить печать, чтобы уменьшить получаемый целью урон на 1d10 + половину вашего уровня иллриггера, округлённую вниз.</content>
<content contentuid="hdeae353cg3e01gc0ceg92b4g4b51942b775d" version="2">Когда вы попадаете по существу размера «Большой» или меньше атакой оружием ближнего боя, удерживаемым двумя руками, вы можете оттолкнуть цель на 10 футов по горизонтали.</content>
<content contentuid="hdf198f7dg49f7g0b3cg9da5g4e2dae846d69" version="2">Уровень 2: Ложь</content>
<content contentuid="he0219d6eg9988g1637g2557g9b2d2d0fa8aa" version="1">Порча</content>
<content contentuid="he346a671g0b23g7417g4182g5a8d53b45077" version="1">Вы можете наполнять свои печати адской магической силой, усиливая их эффекты.&lt;br&gt;&lt;br&gt;Некоторые дары требуют минимального уровня иллриггера. Достигнув 7-го уровня, вы получаете ещё один дополнительный дар.</content>
<content contentuid="he8bb0a53gb20cg63a2g3f35gf408f22a699e" version="4">Уровень 7: Выстрел возмездия</content>
<content contentuid="hea106602g9f5dg8754g958dg4324799b9073" version="1">Обескровливание</content>
<content contentuid="hea661ec8gedc0g2cfagc3ffg287756f25a33" version="1">Покров теней</content>
<content contentuid="hebd66c01g7f34g9436g6f68gde5a37620a3f" version="1">Высвобождение ада: Некротическая энергия</content>
<content contentuid="heca0b0d3g54dbgf86cg916cg906977301869" version="1">Когда вы сжигаете одну или несколько печатей на осуждённом существе, вы можете реакцией высвободить вокруг него взрыв адской энергии. Каждое выбранное вами существо в пределах 10 футов от цели должно совершить спасбросок Ловкости. При провале существо получает урон того же типа и в том же количестве, что нанесли печати осуждённому существу. При успехе существо получает половину этого урона.</content>
<content contentuid="hecedcd5eg3f81g2638g3af5gefcca2c219fb" version="1">Сжигание печатей: Огонь</content>
<content contentuid="hf1c1d608gab6fgea97g5ce7g12f4e4ddb16c" version="1">Уровень 3: Благословение Сутеха</content>
<content contentuid="hf1d142d7g41e9g9a72g6d5cgf6c2fa588579" version="2">Вы получаете бонус +1 к спасброскам за каждое враждебное существо в пределах 10 футов от вас, максимум до +5.</content>
<content contentuid="hf204ad45gef57gf823g5fbdg2aadc2b21ace" version="1">Уровень 11: Ужасающая сила (Холод)</content>
<content contentuid="hf6bff230g1643g366bg93ffg3c9d643ce07c" version="4">Уровень 7: Высвобождение ада</content>
<content contentuid="hf94f341dged44gb9bcged64g8666ae8b410e" version="1">Бонусным действием вы восстанавливаете количество очков здоровья, равное пятикратному уровню вашего иллриггера, распределяя это исцеление по своему выбору между собой и другими существами в пределах 30 футов от вас.</content>
<content contentuid="hfa888bb3gb8a0ge2a8g81c2g00ae8844f692" version="4">Уровень 7: Цепь Ахерона</content>
<content contentuid="hfca57e47ged90g177fgfc94gdd2a6f69f1a8" version="1">Сжигание печатей: Некротическая энергия</content>
<content contentuid="hfe1fd654g95d3g0e72g524ag183786863374" version="1">Не может совершать бонусные действия или реакции.</content>
</contentList>

View File

@@ -32,7 +32,7 @@
<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="281485714128896"/>
<attribute id="Version64" type="int64" value="281492156579841"/>
<children>
<node id="PublishVersion">
<attribute id="Version64" type="int64" value="281477124194304"/>

View File

@@ -86,7 +86,7 @@ if (-not (Test-Path -LiteralPath $resolvedEditsPath)) {
throw "Edits file was not found: '$resolvedEditsPath'."
}
$edits = Get-Content -LiteralPath $resolvedEditsPath -Raw | ConvertFrom-Json -Depth 10
$edits = Get-Content -LiteralPath $resolvedEditsPath -Raw | ConvertFrom-Json
if ($null -eq $edits) {
throw "Edits file is empty or invalid JSON: '$resolvedEditsPath'."
}

View File

@@ -20,7 +20,8 @@ $ErrorActionPreference = "Stop"
function Convert-VersionTagToVersion64 {
param(
[string]$Tag,
[string]$FallbackVersion64
[string]$FallbackVersion64,
[string]$RepoPath
)
if (-not $Tag) {
@@ -32,16 +33,31 @@ function Convert-VersionTagToVersion64 {
$normalized = $normalized.Substring(1)
}
if ($normalized -notmatch '^\d+(\.\d+){0,3}$') {
return [int64]$FallbackVersion64
if ($normalized -notmatch '^(?<base>\d+\.\d+\.\d+)(?:-(?<suffix>[0-9A-Za-z][0-9A-Za-z.-]*))?$') {
throw "Version tag '$Tag' is invalid. Expected format: vX.Y.Z or vX.Y.Z-suffix"
}
$parts = $normalized.Split(".")
$baseVersion = $Matches.base
$suffix = $Matches.suffix
$parts = $baseVersion.Split(".")
$numbers = @(0, 0, 0, 0)
for ($i = 0; $i -lt $parts.Length; $i++) {
$numbers[$i] = [int]$parts[$i]
}
if ($suffix) {
$resolvedRepoPath = [System.IO.Path]::GetFullPath($RepoPath)
$matchingTags = @()
try {
$matchingTags = @(git -C $resolvedRepoPath tag --list "v$baseVersion-*" 2>$null | Where-Object { $_ -and $_ -ne $Tag })
} catch {
$matchingTags = @()
}
$numbers[3] = $matchingTags.Count + 1
}
return ([int64]$numbers[0] -shl 55) -bor ([int64]$numbers[1] -shl 47) -bor ([int64]$numbers[2] -shl 31) -bor [int64]$numbers[3]
}
@@ -59,7 +75,7 @@ if ($VersionTag) {
}
$zipPath = Join-Path $buildPath "$archiveName.zip"
$infoJsonPath = Join-Path $buildPath "info.json"
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag -FallbackVersion64 $ModVersion64
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag -FallbackVersion64 $ModVersion64 -RepoPath $workspacePath
if (-not (Test-Path -LiteralPath $DivinePath)) {
$resolvedCommand = Get-Command $DivinePath -ErrorAction SilentlyContinue

View File

@@ -0,0 +1,113 @@
[CmdletBinding()]
param(
[Parameter()]
[string]$BotToken,
[Parameter()]
[string]$ChatId,
[Parameter()]
[string]$ThreadId,
[Parameter(Mandatory = $true)]
[string]$Text,
[switch]$DisableNotification
)
$ErrorActionPreference = "Stop"
function Import-DotEnvFile {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
return
}
foreach ($line in [System.IO.File]::ReadAllLines($Path)) {
$trimmedLine = $line.Trim()
if (-not $trimmedLine -or $trimmedLine.StartsWith("#")) {
continue
}
$separatorIndex = $trimmedLine.IndexOf("=")
if ($separatorIndex -lt 1) {
continue
}
$name = $trimmedLine.Substring(0, $separatorIndex).Trim()
$value = $trimmedLine.Substring($separatorIndex + 1).Trim()
if (
($value.StartsWith('"') -and $value.EndsWith('"')) -or
($value.StartsWith("'") -and $value.EndsWith("'"))
) {
$value = $value.Substring(1, $value.Length - 2)
}
[System.Environment]::SetEnvironmentVariable($name, $value, "Process")
}
}
function Resolve-Setting {
param(
[string]$ExplicitValue,
[Parameter(Mandatory = $true)]
[string]$EnvName
)
if (-not [string]::IsNullOrWhiteSpace($ExplicitValue)) {
return $ExplicitValue
}
$envValue = [System.Environment]::GetEnvironmentVariable($EnvName, "Process")
if (-not [string]::IsNullOrWhiteSpace($envValue)) {
return $envValue
}
return $null
}
$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot ".."))
$localEnvPath = Join-Path $repoRoot ".env.local"
Import-DotEnvFile -Path $localEnvPath
$BotToken = Resolve-Setting -ExplicitValue $BotToken -EnvName "BOT_TOKEN"
$ChatId = Resolve-Setting -ExplicitValue $ChatId -EnvName "TG_CHAT_ID"
$ThreadId = Resolve-Setting -ExplicitValue $ThreadId -EnvName "TG_THREAD_ID"
if ([string]::IsNullOrWhiteSpace($BotToken)) {
throw "Telegram bot token is required. Pass -BotToken or set BOT_TOKEN in .env.local."
}
if ([string]::IsNullOrWhiteSpace($ChatId)) {
throw "Telegram chat id is required. Pass -ChatId or set TG_CHAT_ID in .env.local."
}
if ([string]::IsNullOrWhiteSpace($ThreadId)) {
throw "Telegram thread id is required. Pass -ThreadId or set TG_THREAD_ID in .env.local."
}
$normalizedText = $Text.
Replace("``r``n", "`r`n").
Replace("``n", "`n").
Replace("%0A", "`n")
$body = @{
chat_id = $ChatId
message_thread_id = $ThreadId
parse_mode = "HTML"
text = $normalizedText
}
if ($DisableNotification) {
$body.disable_notification = "true"
}
Invoke-RestMethod `
-Method Post `
-Uri ("https://api.telegram.org/bot{0}/sendMessage" -f $BotToken) `
-Body $body | Out-Null

View File

@@ -1,14 +1,16 @@
param(
[Parameter(Mandatory = $true)]
[string]$VersionTag,
[string]$MetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx"
[string]$MetaPath = "Mods/DnD 5.5e AIO Russian/meta.lsx",
[string]$RepositoryPath = "."
)
$ErrorActionPreference = "Stop"
function Convert-VersionTagToVersion64 {
function Get-ReleaseVersionParts {
param(
[string]$Tag
[string]$Tag,
[string]$RepoPath
)
$normalized = $Tag
@@ -16,17 +18,36 @@ function Convert-VersionTagToVersion64 {
$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"
if ($normalized -notmatch '^(?<base>\d+\.\d+\.\d+)(?:-(?<suffix>[0-9A-Za-z][0-9A-Za-z.-]*))?$') {
throw "Version tag '$Tag' is invalid. Expected format: vX.Y.Z or vX.Y.Z-suffix"
}
$parts = $normalized.Split(".")
$baseVersion = $Matches.base
$suffix = $Matches.suffix
$parts = $baseVersion.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]
if ($suffix) {
$resolvedRepoPath = [System.IO.Path]::GetFullPath($RepoPath)
$matchingTags = @()
try {
$matchingTags = @(git -C $resolvedRepoPath tag --list "v$baseVersion-*" 2>$null | Where-Object { $_ -and $_ -ne $Tag })
} catch {
$matchingTags = @()
}
$numbers[3] = $matchingTags.Count + 1
}
return [pscustomobject]@{
BaseVersion = $baseVersion
Suffix = $suffix
Version64 = ([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)
@@ -34,7 +55,8 @@ if (-not (Test-Path -LiteralPath $resolvedMetaPath)) {
throw "meta.lsx was not found: '$resolvedMetaPath'."
}
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag
$releaseVersion = Get-ReleaseVersionParts -Tag $VersionTag -RepoPath $RepositoryPath
$resolvedVersion64 = $releaseVersion.Version64
$utf8Encoding = [System.Text.UTF8Encoding]::new($false)
$metaContent = [System.IO.File]::ReadAllText($resolvedMetaPath, $utf8Encoding)
[xml]$metaXml = $metaContent
@@ -60,4 +82,4 @@ $updatedMeta = [regex]::Replace(
[System.IO.File]::WriteAllText($resolvedMetaPath, $updatedMeta, $utf8Encoding)
Write-Host "[set-version.ps1] Updated '$resolvedMetaPath' to Version64=$resolvedVersion64 (from tag '$VersionTag')."
Write-Host "[set-version.ps1] Updated '$resolvedMetaPath' to Version64=$resolvedVersion64 (from tag '$VersionTag', base '$($releaseVersion.BaseVersion)')."