Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f8233ab0b | |||
| af1fd046d4 | |||
| d82c627bb1 | |||
| d17bd723e3 | |||
| fe8c50ff80 | |||
| f9289b4384 | |||
| 5ac424770d | |||
| a29c22af12 | |||
| b684bf195e | |||
| 39ac7a54ce | |||
| 78a9840349 | |||
| 92c78f0813 | |||
| 07c7fc8aa7 | |||
| cb37f422ac | |||
| 72bab59520 | |||
| 1ca6d579da | |||
| 6ede25dc35 | |||
| de85438afe | |||
| 111cf8c269 | |||
| d2299199ba |
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
BOT_TOKEN=
|
||||||
|
TG_CHAT_ID=
|
||||||
|
TG_THREAD_ID=
|
||||||
@@ -13,6 +13,9 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on:
|
runs-on:
|
||||||
- win11
|
- win11
|
||||||
|
env:
|
||||||
|
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||||
|
TG_THREAD_ID: ${{ secrets.TG_THREAD_ID }}
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
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 --depth 1 origin $repoRef
|
||||||
|
git -c "http.extraHeader=Authorization: Basic $authBasic" fetch --force --tags origin
|
||||||
git checkout --force FETCH_HEAD
|
git checkout --force FETCH_HEAD
|
||||||
|
|
||||||
if (-not (Test-Path -LiteralPath "Mods\\DnD 5.5e AIO Russian\\Localization\\Russian\\russian.xml")) {
|
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 ".tools\\lslib" -Force | Out-Null
|
||||||
New-Item -ItemType Directory -Path "build" -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
|
- name: Download latest LSLib release
|
||||||
run: |
|
run: |
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -109,6 +124,7 @@ jobs:
|
|||||||
throw "Release archive was not found at '$zipPath'."
|
throw "Release archive was not found at '$zipPath'."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$isPrerelease = $tagName -match '^v\d+\.\d+\.\d+-'
|
||||||
$owner = $repoParts[0]
|
$owner = $repoParts[0]
|
||||||
$repo = $repoParts[1]
|
$repo = $repoParts[1]
|
||||||
$apiBase = "$serverUrl/api/v1/repos/$owner/$repo"
|
$apiBase = "$serverUrl/api/v1/repos/$owner/$repo"
|
||||||
@@ -137,10 +153,19 @@ jobs:
|
|||||||
name = $tagName
|
name = $tagName
|
||||||
target_commitish = "${{ gitea.sha }}"
|
target_commitish = "${{ gitea.sha }}"
|
||||||
draft = $false
|
draft = $false
|
||||||
prerelease = $false
|
prerelease = $isPrerelease
|
||||||
} | ConvertTo-Json
|
} | ConvertTo-Json
|
||||||
|
|
||||||
$release = Invoke-RestMethod -Method Post -Uri "$apiBase/releases" -Headers $headers -ContentType "application/json" -Body $releaseBody
|
$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
|
$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
|
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
186
.github/workflows/build.yml
vendored
Normal 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
4
.gitignore
vendored
@@ -2,6 +2,10 @@ build/
|
|||||||
build-stage*
|
build-stage*
|
||||||
.tools/
|
.tools/
|
||||||
.cache/
|
.cache/
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
*.pak
|
*.pak
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|||||||
293
AGENTS.md
293
AGENTS.md
@@ -1,91 +1,236 @@
|
|||||||
# AGENTS.md
|
# 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)
|
## Communication (MUST)
|
||||||
- Do not leave temporary/debug artifacts in repo.
|
- Answer first, then request approval if needed.
|
||||||
- Remove additional debug/temp dirs unless user asked to keep them.
|
- Concise, meaningful, no filler.
|
||||||
|
- Do not end response with only procedural choice.
|
||||||
|
|
||||||
### Rules Maintenance (General)
|
Approval/clarification:
|
||||||
- For changes to rules files (`AGENTS.md`, `ACTIONS.md`): prefer optimized, compressed edits for AI-agent execution (machine-readable, unambiguous).
|
- ask once, no repetition
|
||||||
- Keep rule updates minimal and non-duplicative: merge overlapping points, remove redundancy, preserve intent.
|
- binary → yes/no
|
||||||
|
- multiple → numbered options + brief context
|
||||||
|
|
||||||
### Communication (General)
|
- File links in repo docs/checklists: relative paths, `/`, spaces as `%20`.
|
||||||
- Project file links in user-facing Markdown: relative paths, `/` separators, spaces encoded 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
|
## Git Workflow (MUST)
|
||||||
- Repository purpose: standalone Russian localization mod only.
|
- Never commit/push without explicit user approval.
|
||||||
- Allowed domain: localization content + packaging/release metadata.
|
- After approval → commit + push immediately.
|
||||||
- Forbidden: gameplay logic, Script Extender content, unrelated assets.
|
- Commit messages: Russian, factual (what was done).
|
||||||
- Keep repository source-only.
|
- 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.
|
||||||
- Never commit `.pak` or temporary build artifacts.
|
|
||||||
|
|
||||||
### Canonical Paths
|
After work in `fix/*` or `feat/*`:
|
||||||
- Mod sources: `Mods/DnD 5.5e AIO Russian`
|
1. create PR/MR targeting `main`
|
||||||
- Russian localization: `Mods/DnD 5.5e AIO Russian/Localization/Russian/russian.xml`
|
2. merge changes into `main` and delete the source branch
|
||||||
- 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`
|
|
||||||
|
|
||||||
### Packaging Invariants
|
Push failure:
|
||||||
- `.pak` must contain only BG3 mod structure under `Mods/...`.
|
- retry ≤2 times, 3s delay
|
||||||
- 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.
|
|
||||||
|
|
||||||
### Build/CI Contract
|
Release link:
|
||||||
- CI workflow stays thin:
|
- provide `[version](url)` immediately after tag push, without waiting for CI
|
||||||
1. prepare workspace
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
2. download Divine
|
||||||
3. call `scripts/build.ps1`
|
3. run `scripts/build.ps1`
|
||||||
4. publish tag archive
|
4. publish
|
||||||
- Expected build outputs:
|
|
||||||
- `build/DnD 5.5e AIO Russian.pak`
|
Outputs:
|
||||||
|
- `build/*.pak`
|
||||||
- `build/info.json`
|
- `build/info.json`
|
||||||
- `build/DnD 5.5e AIO Russian <tag>.zip` (for tag builds)
|
- `build/*.zip` (tag only)
|
||||||
- Release ZIP must include only `.pak` + `info.json`.
|
|
||||||
- CI triggers: tag `v*` and manual dispatch; not every push to `main`.
|
|
||||||
|
|
||||||
### Version/Release Rules
|
Release ZIP:
|
||||||
- Read release version only from `save/region[@id="Config"]/node[@id="root"]/children/node[@id="ModuleInfo"]/attribute[@id="Version64"]` via explicit XML parsing.
|
- only `.pak` + `info.json`
|
||||||
- `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`.
|
|
||||||
|
|
||||||
### info.json Contract
|
Triggers:
|
||||||
- Top-level keys: `Mods`, `MD5`.
|
- automatic: push tag `v*`
|
||||||
- Per-mod keys: `Author`, `Name`, `Folder`, `Version`, `Description`, `UUID`, `Created`, `Dependencies`, `Group`.
|
- manual: workflow_dispatch
|
||||||
- `Dependencies` is an array of UUIDs.
|
- branch pushes without tag MUST NOT publish release artifacts
|
||||||
- Current dependency UUID: `897914ef-5c96-053c-44af-0be823f895fe`.
|
|
||||||
|
|
||||||
### 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)
|
## Versioning (CRITICAL)
|
||||||
- Ignored/temp patterns include: `build/`, `build-stage*`, `.tools/`, `*.pak`.
|
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.
|
||||||
|
|||||||
@@ -2023,9 +2023,7 @@
|
|||||||
|
|
||||||
Пламя огня заклинаний. Вы изучаете заговор «Священное пламя». Также вы можете накладывать этот заговор бонусным действием количество раз, равное вашему бонусу мастерства, восстанавливая все потраченные использования после завершения долгого отдыха.</content>
|
Пламя огня заклинаний. Вы изучаете заговор «Священное пламя». Также вы можете накладывать этот заговор бонусным действием количество раз, равное вашему бонусу мастерства, восстанавливая все потраченные использования после завершения долгого отдыха.</content>
|
||||||
<content contentuid="h502820d0g8b37g078bg65f7g4a80446379d8" version="1">Наёмник Жентарима</content>
|
<content contentuid="h502820d0g8b37g078bg65f7g4a80446379d8" version="1">Наёмник Жентарима</content>
|
||||||
<content contentuid="hdeab958dge913gf95bg123dg27338a365d6d" version="2">Черта: Громилa Жентарима<br><br>Возможно, вам нужны были деньги. Возможно, вы жаждали найти семью, какой бы сомнительной она ни была. Или, может, вы просто умеете добиваться своего любыми средствами. Какой бы ни была ваша причина, вы вступили в ряды Жентарима — самой одиозной наёмнической гильдии в Царствах. Хотя лидеры Жентарима настаивают, что их организация больше похожа на семью, чем на теневой синдикат, мало какие семьи демонстрируют столько же обмана, кумовства и коррупции. Вы отточили свою хитрость, реакцию и владение клинком, чтобы подняться по иерархии гильдии.<br><br>Пользоваться моментом. Когда вы бросаете урон от Атаки возможности, вы можете бросить кости урона дважды и выбрать любой результат против цели.
|
<content contentuid="hdeab958dge913gf95bg123dg27338a365d6d" version="2">Черта: Громила Жентарима<br><br>Возможно, вам нужны были деньги. Возможно, вы жаждали найти семью, какой бы сомнительной она ни была. Или, может, вы просто умеете добиваться своего любыми средствами. Какой бы ни была ваша причина, вы вступили в ряды Жентарима — самой одиозной наёмнической гильдии в Царствах. Хотя лидеры Жентарима настаивают, что их организация больше похожа на семью, чем на теневой синдикат, мало какие семьи демонстрируют столько же обмана, кумовства и коррупции. Вы отточили свою хитрость, реакцию и владение клинком, чтобы подняться по иерархии гильдии.<br><br>Пользоваться моментом. Когда вы бросаете урон от Атаки возможности, вы можете бросить кости урона дважды и выбрать любой результат против цели.<br><br>Семья прежде всего. Один раз за Долгий отдых вы даруете себе и союзникам в пределах 30 футов от вас преимущество на броски инициативы.</content>
|
||||||
|
|
||||||
Семья прежде всего. Один раз за Долгий отдых вы даруете себе и союзникам в пределах 30 футов от вас преимущество на броски инициативы.</content>
|
|
||||||
<content contentuid="h6c0d30c6gd6bagd6f9g6ed0g7d30fcd174dd" version="1">Черта: Ледяной чародей</content>
|
<content contentuid="h6c0d30c6gd6bagd6f9g6ed0g7d30fcd174dd" version="1">Черта: Ледяной чародей</content>
|
||||||
<content contentuid="he5401f43ga2cfgcd9dgcb25g6ce8548e686a" version="1">Заговор. Вы изучаете заговор «Луч холода».
|
<content contentuid="he5401f43ga2cfgcd9dgcb25g6ce8548e686a" version="1">Заговор. Вы изучаете заговор «Луч холода».
|
||||||
|
|
||||||
@@ -2130,19 +2128,19 @@
|
|||||||
<content contentuid="h8bfddb10g014ag1ffagebccgfc7f04dceadc" version="1">Смертоносная стрельба</content>
|
<content contentuid="h8bfddb10g014ag1ffagebccgfc7f04dceadc" version="1">Смертоносная стрельба</content>
|
||||||
<content contentuid="h76568432gfb37gc67cgcb8egf3cebfdfa094" version="1">Бонусным действием вы получаете Преимущество на проверку атаки из дальнобойного оружия в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этом ходу, а после его использования ваша скорость становится 0 до конца хода.</content>
|
<content contentuid="h76568432gfb37gc67cgcb8egf3cebfdfa094" version="1">Бонусным действием вы получаете Преимущество на проверку атаки из дальнобойного оружия в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этом ходу, а после его использования ваша скорость становится 0 до конца хода.</content>
|
||||||
<content contentuid="h93af975eg1568g7912g8166g2000a25d6098" version="1">Отблеск Гнева</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="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="hec257629g3869gaf27g7d40g1a0a389564d4" version="1">Ролевая игра «Властелин Колец»</content>
|
||||||
<content contentuid="h5660c039gdc35g3a65gfe67gdd812981ab72" version="1">Ролевая игра «Властелин Колец»</content>
|
<content contentuid="h5660c039gdc35g3a65gfe67gdd812981ab72" version="1">Ролевая игра «Властелин Колец»</content>
|
||||||
<content contentuid="h8e50ea07g829fg41c1gfdaeg0618e6beca34" version="1">Смертоносная стрельба</content>
|
<content contentuid="h8e50ea07g829fg41c1gfdaeg0618e6beca34" version="1">Смертоносная стрельба</content>
|
||||||
<content contentuid="h0f8ca247g66e5g6260ga870g3e352f39fa6e" version="2">Эльфы обладают врождённым талантом к точной стрельбе из лука. Вы отточили этот дар практически до совершенства, и ваши стрелы находят цель с поразительной точностью.<br><br>Бонусным действием вы можете даровать себе преимущество на бросок атаки дистанционным оружием в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этот ход, и после его использования ваша скорость становится 0 до конца текущего хода.</content>
|
<content contentuid="h0f8ca247g66e5g6260ga870g3e352f39fa6e" version="2">Эльфы обладают врождённым талантом к точной стрельбе из лука. Вы отточили этот дар практически до совершенства, и ваши стрелы находят цель с поразительной точностью.<br><br>Бонусным действием вы можете даровать себе преимущество на бросок атаки дистанционным оружием в текущем ходу. Вы можете использовать это бонусное действие только если не перемещались в этот ход, и после его использования ваша скорость становится 0 до конца текущего хода.</content>
|
||||||
<content contentuid="h6e348a1aga0b0g9788gb85dgeba21fa79668" version="1">Драконоборец</content>
|
<content contentuid="h6e348a1aga0b0g9788gb85dgeba21fa79668" version="1">Драконоборец</content>
|
||||||
<content contentuid="ha77cb967ga90dgf3bagbb92g9b2af176e030" version="2">Легенда о Барде Лучнике вдохновила многих юношей и девушек из Долины, заставив их жаждать доказать свою ценность убийством великого чудовища. Как и многие до вас, вы долго размышляли над способами борьбы с существами крупных размеров, надеясь однажды снискать славу, победив их.<br><brВы совершаете броски атаки с преимуществом против крупных и более крупных существ. Кроме того, при попадании по таким существам вы наносите дополнительный урон, равный вашему модификатору Силы.</content>
|
<content contentuid="ha77cb967ga90dgf3bagbb92g9b2af176e030" version="2">Легенда о Барде-Лучнике вдохновила многих юношей и девушек из Долины, заставив их жаждать доказать свою ценность убийством великого чудовища. Как и многие до вас, вы долго размышляли над способами борьбы с существами крупных размеров, надеясь однажды снискать славу, победив их.<br><br>Вы совершаете броски атаки с преимуществом против крупных и более крупных существ. Кроме того, при попадании по таким существам вы наносите дополнительный урон, равный вашему модификатору Силы.</content>
|
||||||
<content contentuid="h27541f87g50d6gf441gbab6geae246f77643" version="1">Мощный выстрел</content>
|
<content contentuid="h27541f87g50d6gf441gbab6geae246f77643" version="1">Мощный выстрел</content>
|
||||||
<content contentuid="ha574dfdfgf055g69c1g9889gce2fffa674e6" version="2">Хотя Чёрная стрела, сразившая дракона Смауга, возможно, была предназначена для этого судьбой, рука, запустившая её с такой силой, была невероятно крепка. Когда вы мечете копье или натягиваете лук, вы следите за тем, чтобы ваш хват был твёрдым, а прицел верным.<br><br>Совершая атаку дистанционным оружием, вы используете модификатор Силы для бросков атаки и урона. Вы должны использовать один и тот же модификатор для обоих бросков.</content>
|
<content contentuid="ha574dfdfgf055g69c1g9889gce2fffa674e6" version="2">Хотя Чёрная стрела, сразившая дракона Смауга, возможно, была предназначена для этого судьбой, рука, запустившая её с такой силой, была невероятно крепка. Когда вы мечете копье или натягиваете лук, вы следите за тем, чтобы ваш хват был твёрдым, а прицел верным.<br><br>Совершая атаку дистанционным оружием, вы используете модификатор Силы для бросков атаки и урона. Вы должны использовать один и тот же модификатор для обоих бросков.</content>
|
||||||
<content contentuid="h2ffcb591g0cc8ga4f6g50f8g0b3e5ae7dcf0" version="1">Отблеск Гнева</content>
|
<content contentuid="h2ffcb591g0cc8ga4f6g50f8g0b3e5ae7dcf0" version="1">Отблеск Гнева</content>
|
||||||
<content contentuid="h63f9716cgc2d1g6fcbg46fcg4c5e77c4be1d" version="2">Ваш народ видел множество поражений и множество бесплодных побед в войнах против Тени. Смертоносная ярость, которую ваши сородичи питают к Врагу, наполняет ваше оружие отблеском холодного пламени.<br><br>Бонусным действием вы можете заставить wieldемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.<br><br>При попадании критический удар атакой оружия ближнего боя, вы можете бросить одну из костей урона этого оружия дополнительным раз и добавить результат.</content>
|
<content contentuid="h63f9716cgc2d1g6fcbg46fcg4c5e77c4be1d" version="2">Ваш народ видел множество поражений и множество бесплодных побед в войнах против Тени. Смертоносная ярость, которую ваши сородичи питают к Врагу, наполняет ваше оружие отблеском холодного пламени.<br><br>Бонусным действием вы можете заставить удерживаемое вами оружие ближнего боя излучать тусклый свет в радиусе 10 футов на 10 ходов. Этот свет является солнечным. Пока оружие сияет, оно наносит урон излучением вместо своего обычного вида урона.<br><br>При критическом попадании атакой оружием ближнего боя вы можете ещё раз бросить один из кубиков урона этого оружия и добавить результат.</content>
|
||||||
<content contentuid="hbae36f77gae16gae82g11feg104123766490" version="1">Проворство</content>
|
<content contentuid="hbae36f77gae16gae82g11feg104123766490" version="1">Проворство</content>
|
||||||
<content contentuid="h624efb18g8179g6ce0gbe0fg03d310683b76" version="2">Ваше мастерство (или везение?) в бою растёт вместе с набранным опытом.<br><br>Вы получаете бонус +1 к КБ. Вы теряете этот бонус, если находитесь в состоянии недееспособности или держите щит.</content>
|
<content contentuid="h624efb18g8179g6ce0gbe0fg03d310683b76" version="2">Ваше мастерство (или везение?) в бою растёт вместе с набранным опытом.<br><br>Вы получаете бонус +1 к КБ. Вы теряете этот бонус, если находитесь в состоянии недееспособности или держите щит.</content>
|
||||||
<content contentuid="hc2022d67g4441g1ca0g979ag85847dde7f08" version="2">Раса: Аасимар</content>
|
<content contentuid="hc2022d67g4441g1ca0g979ag85847dde7f08" version="2">Раса: Аасимар</content>
|
||||||
@@ -2589,11 +2587,7 @@
|
|||||||
<content contentuid="h3b7d345ag5345g49ffg6c00g90a138a19f95" version="1">Находясь в зверином облике, вы не можете говорить и накладывать заклинания. Вы принимаете характеристики своей звериной формы, за исключением значений <LSTag Tooltip="Intelligence">Интеллекта</LSTag>, <LSTag Tooltip="Wisdom">Мудрости</LSTag> и <LSTag Tooltip="Charisma">Харизмы</LSTag>.</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="h3d2a0062g8a04g4942g776fg8389d4b68f1b" version="1">Примите облик белого медведя, который может <LSTag Type="Spell" Tooltip="Shout_GoadingRoar_Bear_Summon">Провоцировать</LSTag> врагов атаковать его.</content>
|
||||||
<content contentuid="h4afbde24g3b4ag6245gdfc5gf40fbb6dd7b9" version="1">Уровень 3: Бах! И ты труп!</content>
|
<content contentuid="h4afbde24g3b4ag6245gdfc5gf40fbb6dd7b9" version="1">Уровень 3: Бах! И ты труп!</content>
|
||||||
<content contentuid="h4b3edb5dg700eg11d2gb46eg867808ec1738" version="2">Ваш опыт борьбы с заклинателями даёт вам следующие преимущества.<br><br>Разрушитель концентрации. Каждый раз, когда вы совершаете дальнобойный бросок атаки, вы нарушаете защитную магию, действующую на цель, и прерываете её концентрацию.
|
<content contentuid="h4b3edb5dg700eg11d2gb46eg867808ec1738" version="2">Ваш опыт борьбы с заклинателями даёт вам следующие преимущества.<br><br>Разрушитель защитной магии. Каждый раз, когда вы совершаете дальнобойный бросок атаки, вы временно нарушаете защитную магию, действующую на цель. На время этой атаки эффекты заклинаний, нацеленных на существо, такие как «Доспех мага», а также свойства и силы магических предметов, которые существо носит или держит, подавляются и не действуют. Цель этой атаки не может Реакцией накладывать заклинания вроде «Щита» в ответ на атаку или её урон.<br><br>Антимагический выстрел. Когда вы совершаете критическое попадание по цели под действием вашей особенности «Выстрел в живот», вы также подавляете её способность накладывать заклинания. Пока снаряд остаётся в цели, она не может накладывать заклинания или выполнять действие «Магия».<br><br>Закалённый магией. Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить результат к броску, потенциально превращая провал в успех.</content>
|
||||||
|
|
||||||
Антимагический выстрел. Когда вы совершаете критическое попадание по цели под действием вашей особенности «Выстрел в живот», вы также подавляете её способность накладывать заклинания. Пока снаряд остаётся в цели, она не может накладывать заклинания или выполнять действие «Магия».
|
|
||||||
|
|
||||||
Закалённый магией. Когда вы проваливаете спасбросок против заклинания или магического эффекта, вы можете Реакцией бросить 1d6 и добавить результат к броску, потенциально превращая провал в успех.</content>
|
|
||||||
<content contentuid="h5ef73b58ga233g8fccg4389gcd443dd5c7d7" version="1">Закалённый магией</content>
|
<content contentuid="h5ef73b58ga233g8fccg4389gcd443dd5c7d7" version="1">Закалённый магией</content>
|
||||||
<content contentuid="h672e82feg2734g7895ga227gd76ffbfded41" version="1">Примите облик гигантского барсука, который может <LSTag Type="Spell" Tooltip="Target_Burrow_GiantBadger">Зарываться</LSTag> в землю.</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="h6827c922gd910gca98gee99gdbd9b0f178b1" version="1">Примите облик глубинного ротэ, который может накладывать <LSTag Type="Spell" Tooltip="Target_DancingLights">Пляшущие огоньки</LSTag> и <LSTag Type="Spell" Tooltip="Rush_Rush_DeepRothe">Таранить</LSTag> врагов.</content>
|
||||||
@@ -2635,4 +2629,126 @@
|
|||||||
<content contentuid="hc1e53854g0170gb211geca0geb2971202e92" version="1">Когда видимый вами союзник в пределах 60 футов получает попадание атакой, вы можете Реакцией потратить одну Кость риска, чтобы совершить атаку дальнобойным оружием по атакующему. При использовании союзник получает Временные очки здоровья, равные выпавшему значению Кости риска.</content>
|
<content contentuid="hc1e53854g0170gb211geca0geb2971202e92" version="1">Когда видимый вами союзник в пределах 60 футов получает попадание атакой, вы можете Реакцией потратить одну Кость риска, чтобы совершить атаку дальнобойным оружием по атакующему. При использовании союзник получает Временные очки здоровья, равные выпавшему значению Кости риска.</content>
|
||||||
<content contentuid="hcf85f5ddg214egc39bga0e8g7ee910e3ffec" version="1">Уровень 3: Призвать к порядку</content>
|
<content contentuid="hcf85f5ddg214egc39bga0e8g7ee910e3ffec" version="1">Уровень 3: Призвать к порядку</content>
|
||||||
<content contentuid="hd43fb4c9g35d7g9ab6g9cc2g37fbd18e9590" version="1">Когда вы совершаете критическое попадание по существу, вы можете потребовать, чтобы цель сдалась. Цель должна преуспеть в спасброске Мудрости против СЛ спасброска ваших манёвров, иначе получит состояние «Испуган» на 1 минуту. Существо может повторять этот спасбросок Мудрости в конце каждого своего хода, оканчивая это состояние при успехе.</content>
|
<content contentuid="hd43fb4c9g35d7g9ab6g9cc2g37fbd18e9590" version="1">Когда вы совершаете критическое попадание по существу, вы можете потребовать, чтобы цель сдалась. Цель должна преуспеть в спасброске Мудрости против СЛ спасброска ваших манёвров, иначе получит состояние «Испуган» на 1 минуту. Существо может повторять этот спасбросок Мудрости в конце каждого своего хода, оканчивая это состояние при успехе.</content>
|
||||||
|
<content contentuid="h00467a34g3b88g453cg081eg6e394afa8e61" version="1">Кровавые рыцари Ада служат Сутеху, Владыке Крови. Их чары высасывают жизненную силу врагов, обращая её в инфернальные ритуалы, способные переломить ход битвы.<br><br>Сутех правит Наракой, Городом Крови. Признанный величайшим чародеем Ада, он носит титул Верховного сангвинария и властвует из Храма Жизненной силы. Он - мастер магии крови, а его ближайший круг жрецов и волшебников составляют кровеличи, неживые заклинатели, чьи телесные формы обратились в прах столетия назад и чьи тела сотворены из цельной крови.<br><br>Все иллриггеры Сутеха принадлежат культу, известному как Чаша Жизненной силы. Рыцари Чаши жадно пьют сущность своих врагов, истощая её, чтобы питать свою магию. Другие члены Ордена Осквернения опасаются, что Кровавые рыцари стремятся не только возвести Сутеха на Трон Ада; некоторые шепчут, что Чаша втайне замышляет сделать Сутеха богом. Разумеется, это было бы изменой.</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-й уровень иллриггера или выше.<br><br>Вы можете бонусным действием потратить печать, чтобы телепортироваться на расстояние до 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 футов вместо касания.<br><br>Кроме того, совершение дальнобойной атаки, пока враждебное существо находится в пределах 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-й уровень иллриггера или выше.<br><br>Когда вы бонусным действием накладываете или перемещаете печать на существо размера «Большой» или меньше, вы можете активировать этот дар, не тратя действия. Вы призываете инфернальные цепи, которые хватают цель и заставляют её совершить спасбросок Силы. При провале вы либо притягиваете существо на 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">Пожертвуйте половиной оставшихся <LSTag Tooltip="HitPoints">очков здоровья</LSTag>, чтобы исцелить цель на то же количество.</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 футов от вас. Вы можете либо наложить эту печать, когда попадаете по цели атакой оружием, не тратя действия, либо бонусным действием наложить её на цель, которую видите в пределах дальности. Эта печать сохраняется, пока не будет сожжена. Существо, на котором есть одна или несколько ваших печатей, считается осуждённым.<br><br>До отдыха вы можете наложить лишь ограниченное число печатей, а все потраченные печати восстанавливаются после короткого или длительного отдыха. На 1-м уровне вы можете иметь максимум три печати, на 3-м уровне - четыре, а на 7-м уровне - пять.<br><br>Если осуждённое существо умирает, вы можете бонусным действием в свой ход переместить все наложенные на него печати на новое существо, которое видите в пределах 30 футов от него.</content>
|
||||||
|
<content contentuid="ha9df2c62g25e2g2614g66dagfe3cb0e26aa5" version="2">Требуется 7-й уровень иллриггера или выше.<br><br>Вы можете бонусным действием потратить печать, чтобы окутать себя или существо, которого касаетесь, мантией из полутеней. Цель получает бонус +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 урона огнём или некротической энергией за каждую сожжённую печать, по вашему выбору. Сожжённая печать немедленно исчезает.<br><br>Достигнув 5-го уровня в этом классе, вы укрепляете связь со своим архидьяволом. Каждая сожжённая печать наносит дополнительный 1d6 урона, всего 2d6 за печать. На 10-м уровне урон каждой печати увеличивается ещё на 1d6, всего до 3d6 за печать.</content>
|
||||||
|
<content contentuid="hc31d5eb8g73cdgf87bg7969geb0fc56b1687" version="1">Кровавые рыцари приносят клятву Сутеху, вступая в Орден Опустошения. Эти заповеди обязывают их владеть кощунственной магией крови, внушать верность и сеять ужас.<br><br>Их сила - их слабость. Я поражаю сильнейших из врагов, ибо их жизненная сила питает мою победу.<br><br>Грех требует страданий. Противостоять мне - ересь. Прежде чем мои враги вкусят поражение, они должны заплатить за своё неверие агонией.<br><br>Верность вознаграждается. Мои дары заставляют союзников зависеть от меня и от кровопролития, которое меня усиливает.<br><br>Милосердие - это сила. Даруя помощь союзникам, я доказываю, сколь велика моя мощь. Каждый раз, когда я возвращаю жизнь, это напоминает, как быстро я могу её отнять.</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">Архидьяволы, правящие Семью Городами Ада, бесконечно плетут козни. Каждый из них вечно замышляет подчинить остальных, взойти на Трон Ада, объединить Семь Городов и всех живущих там инфернальных существ, а затем повести неисчерпаемую армию дьяволов через времена и миры, пока не запылают все вселенные.<br><br>Элитные оперативники этих архидьяволов - иллриггеры. Рыцари, убийцы, маги и террористические коммандос Ада, иллриггеры управляют полем боя, разрушают вражеские союзы и исполняют инфернальную волю своего архидьявола.</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">Вы можете наполнять свои печати адской магической силой, усиливая их эффекты.<br><br>Некоторые дары требуют минимального уровня иллриггера. Достигнув 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>
|
</contentList>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<save>
|
<save>
|
||||||
<version major="4" minor="8" revision="0" build="500"/>
|
<version major="4" minor="8" revision="0" build="500"/>
|
||||||
<region id="Config">
|
<region id="Config">
|
||||||
@@ -9,11 +9,11 @@
|
|||||||
<children>
|
<children>
|
||||||
<node id="ModuleShortDesc">
|
<node id="ModuleShortDesc">
|
||||||
<attribute id="Folder" type="LSString" value="DnD2024_897914ef-5c96-053c-44af-0be823f895fe"/>
|
<attribute id="Folder" type="LSString" value="DnD2024_897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||||
<attribute id="MD5" type="LSString" value="ac497558b070a635abecd7d23d4a3125"/>
|
<attribute id="MD5" type="LSString" value="4bd42ca93f895d1ec521a286bea09ef2"/>
|
||||||
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND"/>
|
<attribute id="Name" type="LSString" value="DnD 5.5e All-in-One BEYOND"/>
|
||||||
<attribute id="PublishHandle" type="uint64" value="4419649"/>
|
<attribute id="PublishHandle" type="uint64" value="4419649"/>
|
||||||
<attribute id="UUID" type="guid" value="897914ef-5c96-053c-44af-0be823f895fe"/>
|
<attribute id="UUID" type="guid" value="897914ef-5c96-053c-44af-0be823f895fe"/>
|
||||||
<attribute id="Version64" type="int64" value="144396678084952064"/>
|
<attribute id="Version64" type="int64" value="144396675937468416"/>
|
||||||
</node>
|
</node>
|
||||||
</children>
|
</children>
|
||||||
</node>
|
</node>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<attribute id="PublishHandle" type="uint64" value="5965149"/>
|
<attribute id="PublishHandle" type="uint64" value="5965149"/>
|
||||||
<attribute id="StartupLevelName" type="FixedString" value=""/>
|
<attribute id="StartupLevelName" type="FixedString" value=""/>
|
||||||
<attribute id="UUID" type="FixedString" value="6401e84d-daf2-416d-adeb-99c03a2487a6"/>
|
<attribute id="UUID" type="FixedString" value="6401e84d-daf2-416d-adeb-99c03a2487a6"/>
|
||||||
<attribute id="Version64" type="int64" value="281483566645248"/>
|
<attribute id="Version64" type="int64" value="281492156579841"/>
|
||||||
<children>
|
<children>
|
||||||
<node id="PublishVersion">
|
<node id="PublishVersion">
|
||||||
<attribute id="Version64" type="int64" value="281477124194304"/>
|
<attribute id="Version64" type="int64" value="281477124194304"/>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ if (-not (Test-Path -LiteralPath $resolvedEditsPath)) {
|
|||||||
throw "Edits file was not found: '$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) {
|
if ($null -eq $edits) {
|
||||||
throw "Edits file is empty or invalid JSON: '$resolvedEditsPath'."
|
throw "Edits file is empty or invalid JSON: '$resolvedEditsPath'."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ $ErrorActionPreference = "Stop"
|
|||||||
function Convert-VersionTagToVersion64 {
|
function Convert-VersionTagToVersion64 {
|
||||||
param(
|
param(
|
||||||
[string]$Tag,
|
[string]$Tag,
|
||||||
[string]$FallbackVersion64
|
[string]$FallbackVersion64,
|
||||||
|
[string]$RepoPath
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not $Tag) {
|
if (-not $Tag) {
|
||||||
@@ -32,16 +33,31 @@ function Convert-VersionTagToVersion64 {
|
|||||||
$normalized = $normalized.Substring(1)
|
$normalized = $normalized.Substring(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($normalized -notmatch '^\d+(\.\d+){0,3}$') {
|
if ($normalized -notmatch '^(?<base>\d+\.\d+\.\d+)(?:-(?<suffix>[0-9A-Za-z][0-9A-Za-z.-]*))?$') {
|
||||||
return [int64]$FallbackVersion64
|
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)
|
$numbers = @(0, 0, 0, 0)
|
||||||
for ($i = 0; $i -lt $parts.Length; $i++) {
|
for ($i = 0; $i -lt $parts.Length; $i++) {
|
||||||
$numbers[$i] = [int]$parts[$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]
|
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"
|
$zipPath = Join-Path $buildPath "$archiveName.zip"
|
||||||
$infoJsonPath = Join-Path $buildPath "info.json"
|
$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)) {
|
if (-not (Test-Path -LiteralPath $DivinePath)) {
|
||||||
$resolvedCommand = Get-Command $DivinePath -ErrorAction SilentlyContinue
|
$resolvedCommand = Get-Command $DivinePath -ErrorAction SilentlyContinue
|
||||||
@@ -101,10 +117,10 @@ if (-not (Test-Path -LiteralPath $stagedMetaPath)) {
|
|||||||
throw "Staged meta.lsx was not found: '$stagedMetaPath'."
|
throw "Staged meta.lsx was not found: '$stagedMetaPath'."
|
||||||
}
|
}
|
||||||
|
|
||||||
$stagedMetaContent = Get-Content -LiteralPath $stagedMetaPath -Raw
|
$utf8Encoding = [System.Text.UTF8Encoding]::new($false)
|
||||||
|
$stagedMetaContent = [System.IO.File]::ReadAllText($stagedMetaPath, $utf8Encoding)
|
||||||
$stagedMetaContent = $stagedMetaContent -replace '(<attribute id="Version64" type="int64" value=")\d+("/>)', "`${1}$resolvedVersion64`${2}"
|
$stagedMetaContent = $stagedMetaContent -replace '(<attribute id="Version64" type="int64" value=")\d+("/>)', "`${1}$resolvedVersion64`${2}"
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
[System.IO.File]::WriteAllText($stagedMetaPath, $stagedMetaContent, $utf8Encoding)
|
||||||
[System.IO.File]::WriteAllText($stagedMetaPath, $stagedMetaContent, $utf8Bom)
|
|
||||||
|
|
||||||
Write-Host "[build.ps1] Staged source tree:"
|
Write-Host "[build.ps1] Staged source tree:"
|
||||||
Get-ChildItem -Recurse $stagingPath | Select-Object FullName, Length | Format-Table -AutoSize
|
Get-ChildItem -Recurse $stagingPath | Select-Object FullName, Length | Format-Table -AutoSize
|
||||||
|
|||||||
113
scripts/send-telegram-notification.ps1
Normal file
113
scripts/send-telegram-notification.ps1
Normal 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
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$VersionTag,
|
[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"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
function Convert-VersionTagToVersion64 {
|
function Get-ReleaseVersionParts {
|
||||||
param(
|
param(
|
||||||
[string]$Tag
|
[string]$Tag,
|
||||||
|
[string]$RepoPath
|
||||||
)
|
)
|
||||||
|
|
||||||
$normalized = $Tag
|
$normalized = $Tag
|
||||||
@@ -16,17 +18,36 @@ function Convert-VersionTagToVersion64 {
|
|||||||
$normalized = $normalized.Substring(1)
|
$normalized = $normalized.Substring(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($normalized -notmatch '^\d+(\.\d+){0,3}$') {
|
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 X.Y.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)
|
$numbers = @(0, 0, 0, 0)
|
||||||
for ($i = 0; $i -lt $parts.Length; $i++) {
|
for ($i = 0; $i -lt $parts.Length; $i++) {
|
||||||
$numbers[$i] = [int]$parts[$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)
|
$resolvedMetaPath = [System.IO.Path]::GetFullPath($MetaPath)
|
||||||
@@ -34,8 +55,10 @@ if (-not (Test-Path -LiteralPath $resolvedMetaPath)) {
|
|||||||
throw "meta.lsx was not found: '$resolvedMetaPath'."
|
throw "meta.lsx was not found: '$resolvedMetaPath'."
|
||||||
}
|
}
|
||||||
|
|
||||||
$resolvedVersion64 = Convert-VersionTagToVersion64 -Tag $VersionTag
|
$releaseVersion = Get-ReleaseVersionParts -Tag $VersionTag -RepoPath $RepositoryPath
|
||||||
$metaContent = Get-Content -LiteralPath $resolvedMetaPath -Raw
|
$resolvedVersion64 = $releaseVersion.Version64
|
||||||
|
$utf8Encoding = [System.Text.UTF8Encoding]::new($false)
|
||||||
|
$metaContent = [System.IO.File]::ReadAllText($resolvedMetaPath, $utf8Encoding)
|
||||||
[xml]$metaXml = $metaContent
|
[xml]$metaXml = $metaContent
|
||||||
|
|
||||||
# Explicitly target ModuleInfo/Version64 via XML path to avoid touching Dependencies/PublishVersion.
|
# Explicitly target ModuleInfo/Version64 via XML path to avoid touching Dependencies/PublishVersion.
|
||||||
@@ -57,7 +80,6 @@ $updatedMeta = [regex]::Replace(
|
|||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
[System.IO.File]::WriteAllText($resolvedMetaPath, $updatedMeta, $utf8Encoding)
|
||||||
[System.IO.File]::WriteAllText($resolvedMetaPath, $updatedMeta, $utf8Bom)
|
|
||||||
|
|
||||||
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)')."
|
||||||
|
|||||||
Reference in New Issue
Block a user