Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e5dceb688 | |||
| 240306da20 | |||
| 3b4175aaff | |||
| a67c18d691 | |||
| 5715da67de | |||
| a2a723a49f | |||
| deb1cc55fa | |||
| 93cbd4eb01 | |||
| 17b2747130 | |||
| 4677df4b80 | |||
| e0916f4845 | |||
| 1cc28b18a1 | |||
| 0c365665c0 | |||
| e730729493 | |||
| 53c33c888d | |||
| 692bcb3b4b | |||
| 4d0f601c7c | |||
| c0a46c7bef | |||
| 04923cc449 | |||
| 4beb01118a | |||
| e06e297540 | |||
| 8d5aac56d1 | |||
| 45c98308fb | |||
| 9896a811f8 | |||
| fd18fcf0bd | |||
| 7928d17ea9 | |||
| 5a873b4e6e | |||
| cac380ca5e | |||
| 792694f6ac | |||
| e644daff99 | |||
| 3b88f4c8eb | |||
| a2fbabedce | |||
| 23084f4cb4 | |||
| 65b3989099 | |||
| a73459ea87 | |||
| c8a439c2f3 | |||
| 311cb768b9 | |||
| 61a8cb40a4 | |||
| ca4a3b6523 |
@@ -0,0 +1,4 @@
|
|||||||
|
*.pyc
|
||||||
|
*__pycache__/
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
*out
|
||||||
|
*logs
|
||||||
|
*actions
|
||||||
|
*notifications
|
||||||
|
*tools
|
||||||
|
plugins
|
||||||
|
user_trunk.yaml
|
||||||
|
user.yaml
|
||||||
|
tmp
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[settings]
|
||||||
|
profile=black
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Prettier friendly markdownlint config (all formatting rules disabled)
|
||||||
|
extends: markdownlint/style/prettier
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Generic, formatter-friendly config.
|
||||||
|
select = ["B", "D3", "E", "F"]
|
||||||
|
|
||||||
|
# Never enforce `E501` (line length violations). This should be handled by formatters.
|
||||||
|
ignore = ["E501"]
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
|
||||||
|
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
|
||||||
|
version: 0.1
|
||||||
|
cli:
|
||||||
|
version: 1.25.0
|
||||||
|
# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins)
|
||||||
|
plugins:
|
||||||
|
sources:
|
||||||
|
- id: trunk
|
||||||
|
ref: v1.7.3
|
||||||
|
uri: https://github.com/trunk-io/plugins
|
||||||
|
# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes)
|
||||||
|
runtimes:
|
||||||
|
enabled:
|
||||||
|
- node@22.16.0
|
||||||
|
- python@3.10.8
|
||||||
|
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
|
||||||
|
lint:
|
||||||
|
enabled:
|
||||||
|
- bandit@1.8.6
|
||||||
|
- black@25.9.0
|
||||||
|
- checkov@3.2.486
|
||||||
|
- git-diff-check
|
||||||
|
- isort@7.0.0
|
||||||
|
- markdownlint@0.45.0
|
||||||
|
- prettier@3.6.2
|
||||||
|
- ruff@0.14.1
|
||||||
|
- trufflehog@3.90.11
|
||||||
|
actions:
|
||||||
|
disabled:
|
||||||
|
- trunk-announce
|
||||||
|
- trunk-check-pre-push
|
||||||
|
- trunk-fmt-pre-commit
|
||||||
|
enabled:
|
||||||
|
- trunk-upgrade-available
|
||||||
+737
@@ -0,0 +1,737 @@
|
|||||||
|
# Release 0.3 Notes
|
||||||
|
|
||||||
|
History compiled from newest to oldest commit. Release-tagged commits are highlighted.
|
||||||
|
|
||||||
|
### 0.3
|
||||||
|
- Date: 2025-10-29
|
||||||
|
- Commit: f2a10ef4
|
||||||
|
- Summary: 0.3
|
||||||
|
- Files: version.nfo
|
||||||
|
|
||||||
|
### sync: nucleares_monitor v1.17
|
||||||
|
- Date: 2025-10-29
|
||||||
|
- Commit: 17b27471
|
||||||
|
- Summary: sync: nucleares_monitor v1.17
|
||||||
|
- Files: nucleares_monitor/version.nfo
|
||||||
|
|
||||||
|
### sync: help_scripts v0.4
|
||||||
|
- Date: 2025-10-29
|
||||||
|
- Commit: 4677df4b
|
||||||
|
- Summary: sync: help_scripts v0.4
|
||||||
|
- Files: help_scripts/sync_foundry_module.py, help_scripts/version.nfo, nucleares_monitor/control_board_monitor.py, nucleares_monitor/tests/test_arrange_windows.py, nucleares_monitor/tests/test_arrangement_fixes.py, nucleares_monitor/tests/test_high_priority_fixes.py (+4 more)
|
||||||
|
|
||||||
|
### Update of conf
|
||||||
|
- Date: 2025-10-22
|
||||||
|
- Commit: e0916f48
|
||||||
|
- Summary: Update of conf
|
||||||
|
- Files: .trunk/.gitignore, .trunk/configs/.isort.cfg, .trunk/configs/.markdownlint.yaml, .trunk/configs/ruff.toml, .trunk/trunk.yaml
|
||||||
|
|
||||||
|
### v 1.17 interim
|
||||||
|
- Date: 2025-10-21
|
||||||
|
- Commit: 1cc28b18
|
||||||
|
- Summary: v 1.17 interim
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py, requirements.txt, test_arrange_windows.py, test_arrangement_fixes.py
|
||||||
|
|
||||||
|
### gitignore
|
||||||
|
- Date: 2025-10-21
|
||||||
|
- Commit: 0c365665
|
||||||
|
- Summary: gitignore
|
||||||
|
- Files: .gitignore
|
||||||
|
|
||||||
|
### v 1.17
|
||||||
|
- Date: 2025-10-21
|
||||||
|
- Commit: e7307294
|
||||||
|
- Summary: v 1.17
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.16-interim2
|
||||||
|
- Date: 2025-10-21
|
||||||
|
- Commit: 53c33c88
|
||||||
|
- Summary: 1.16-interim2
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.16-interim
|
||||||
|
- Date: 2025-10-21
|
||||||
|
- Commit: 692bcb3b
|
||||||
|
- Summary: 1.16-interim
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py, nucleares_monitor/version.nfo
|
||||||
|
|
||||||
|
### v 1.15
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 4d0f601c
|
||||||
|
- Summary: v 1.15
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py, nucleares_monitor/mon.json
|
||||||
|
|
||||||
|
### 1.14
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: c0a46c7b
|
||||||
|
- Summary: 1.14
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py, nucleares_monitor/mon.json, nucleares_monitor/stab_ion.json
|
||||||
|
|
||||||
|
### 1.13
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 04923cc4
|
||||||
|
- Summary: 1.13
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.12
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 4beb0111
|
||||||
|
- Summary: 1.12
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.11
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: e06e2975
|
||||||
|
- Summary: 1.11
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.10
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 8d5aac56
|
||||||
|
- Summary: 1.10
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.9
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 45c98308
|
||||||
|
- Summary: 1.9
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.8
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 9896a811
|
||||||
|
- Summary: 1.8
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.7
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: fd18fcf0
|
||||||
|
- Summary: 1.7
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.6
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 7928d17e
|
||||||
|
- Summary: 1.6
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.5
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 5a873b4e
|
||||||
|
- Summary: 1.5
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.4
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: cac380ca
|
||||||
|
- Summary: 1.4
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.3
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 792694f6
|
||||||
|
- Summary: 1.3
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.2
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: e644daff
|
||||||
|
- Summary: 1.2
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.1
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 3b88f4c8
|
||||||
|
- Summary: 1.1
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 1.0
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: a2fbabed
|
||||||
|
- Summary: 1.0
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.9
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 23084f4c
|
||||||
|
- Summary: 0.9
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.8
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 65b39890
|
||||||
|
- Summary: 0.8
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.7
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: a73459ea
|
||||||
|
- Summary: 0.7
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.6
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: c8a439c2
|
||||||
|
- Summary: 0.6
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.4
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 311cb768
|
||||||
|
- Summary: 0.4
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.3
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: 61a8cb40
|
||||||
|
- Summary: 0.3
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
### 0.1
|
||||||
|
- Date: 2025-10-20
|
||||||
|
- Commit: ca4a3b65
|
||||||
|
- Summary: 0.1
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py
|
||||||
|
|
||||||
|
## RELEASE 0.2
|
||||||
|
### sync: nucleares_monitor v0.1
|
||||||
|
- Date: 2025-10-11
|
||||||
|
- Commit: ec8b3404
|
||||||
|
- Tags: 0.2
|
||||||
|
- Summary: sync: nucleares_monitor v0.1
|
||||||
|
- Files: nucleares_monitor/control_board_monitor.py, nucleares_monitor/version.nfo
|
||||||
|
|
||||||
|
### sync: investigation-board_v13 v0.2
|
||||||
|
- Date: 2025-10-09
|
||||||
|
- Commit: c7048e6c
|
||||||
|
- Summary: sync: investigation-board_v13 v0.2
|
||||||
|
- Files: investigation-board_v13/investigation-board/module.json, investigation-board_v13/investigation-board/scripts/investigation-board.js, investigation-board_v13/version.nfo
|
||||||
|
|
||||||
|
### sync: wg-voidships-builder v1.4.13
|
||||||
|
- Date: 2025-10-09
|
||||||
|
- Commit: 3a610b71
|
||||||
|
- Summary: sync: wg-voidships-builder v1.4.13
|
||||||
|
- Files: wg-voidships-builder/module.json
|
||||||
|
|
||||||
|
### sync: investigation-board_v13 v0.1
|
||||||
|
- Date: 2025-10-09
|
||||||
|
- Commit: 9c06d9ce
|
||||||
|
- Summary: sync: investigation-board_v13 v0.1
|
||||||
|
- Files: investigation-board_v13/version.nfo
|
||||||
|
|
||||||
|
### sync: help_scripts v0.3
|
||||||
|
- Date: 2025-10-09
|
||||||
|
- Commit: a6cdd01e
|
||||||
|
- Summary: sync: help_scripts v0.3
|
||||||
|
- Files: help_scripts/version.nfo
|
||||||
|
|
||||||
|
### sync: foundry v0.2
|
||||||
|
- Date: 2025-10-09
|
||||||
|
- Commit: 0a4148ab
|
||||||
|
- Summary: sync: foundry v0.2
|
||||||
|
- Files: foundry/availability.csv, foundry/components_supplemental_xenos_archeotech.csv, foundry/starship_weapons.csv, foundry/version.nfo, help_scripts/generate_manifests.py, help_scripts/generate_manifestsv2.py (+17 more)
|
||||||
|
|
||||||
|
### sync: wg-voidships_doc v0.2
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 95a71cb6
|
||||||
|
- Summary: sync: wg-voidships_doc v0.2
|
||||||
|
- Files: wg-voidships_doc/version.nfo
|
||||||
|
|
||||||
|
### sync: wg-voidships-journal v1.0.2
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 97f738c5
|
||||||
|
- Summary: sync: wg-voidships-journal v1.0.2
|
||||||
|
- Files: wg-voidships-journal/module.json
|
||||||
|
|
||||||
|
### sync: wg-voidships-builder v1.4.12
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 5ed6c55e
|
||||||
|
- Summary: sync: wg-voidships-builder v1.4.12
|
||||||
|
- Files: wg-voidships-builder/module.json
|
||||||
|
|
||||||
|
### sync: gk-greyknight v0.6.5.3
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 7b621f8b
|
||||||
|
- Summary: sync: gk-greyknight v0.6.5.3
|
||||||
|
- Files: wg-greyknights/module.json
|
||||||
|
|
||||||
|
### sync: help_scripts v0.2
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 64c3b8ca
|
||||||
|
- Summary: sync: help_scripts v0.2
|
||||||
|
- Files: help_scripts/version.nfo
|
||||||
|
|
||||||
|
### sync: foundry v0.1
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 298239c3
|
||||||
|
- Summary: sync: foundry v0.1
|
||||||
|
- Files: foundry/all_hulls.csv, foundry/components_supplemental_xenos_archeotech.csv, foundry/essential_components.csv, foundry/starship_weapons.csv, foundry/version.nfo, foundry/wg-voidship-hulls copy.db (+6 more)
|
||||||
|
|
||||||
|
### sync: help_scripts v0.1
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: ee632ee0
|
||||||
|
- Summary: sync: help_scripts v0.1
|
||||||
|
- Files: help_scripts/sync_foundry_module.py, help_scripts/version.nfo
|
||||||
|
|
||||||
|
### Cleanup part one
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: bf6e1050
|
||||||
|
- Summary: Cleanup part one
|
||||||
|
- Files: foundry.zip, help_scripts/import.py, help_scripts/patch_hulls.py, help_scripts/patch_voidships.py, help_scripts/sound.py, help_scripts/sync_foundry_module.py (+8 more)
|
||||||
|
|
||||||
|
### sync: wg-voidships-builder v1.4.11
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: c2a93cbd
|
||||||
|
- Summary: sync: wg-voidships-builder v1.4.11
|
||||||
|
- Files: wg-voidships-builder/module.json, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/scripts/voidship-actor.js
|
||||||
|
|
||||||
|
### sync: wg-voidships-builder v1.4.10
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 862e0c3c
|
||||||
|
- Summary: sync: wg-voidships-builder v1.4.10
|
||||||
|
- Files: wg-voidships-builder/module.json, wg-voidships-builder/sync_foundry_module.py
|
||||||
|
|
||||||
|
### sync: wg-voidships-builder v1.4.9
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 20345f16
|
||||||
|
- Summary: sync: wg-voidships-builder v1.4.9
|
||||||
|
- Files: wg-voidships-builder/module.json, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/sync_foundry_module.py
|
||||||
|
|
||||||
|
### aaa
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: c11e029b
|
||||||
|
- Summary: aaa
|
||||||
|
- Files: wg-voidships-builder/packs/patch_voidships.py, wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/packs/wg-voidship-hulls.db.bak, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/packs/wg-voidship-items.db.bak, wg-voidships-builder/sync_foundry_module.py
|
||||||
|
|
||||||
|
### updt
|
||||||
|
- Date: 2025-09-19
|
||||||
|
- Commit: 886bced1
|
||||||
|
- Summary: updt
|
||||||
|
- Files: wg-voidships-builder/packs/wg-voidship-archeotech.db, wg-voidships-builder/packs/wg-voidship-xenos.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/templates/voidship-sheet.hbs
|
||||||
|
|
||||||
|
### Working version
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 584985ed
|
||||||
|
- Summary: Working version
|
||||||
|
- Files: wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/templates/voidship-sheet.hbs, wg-voidships-builder/wg-voidships-builder.zip
|
||||||
|
|
||||||
|
### aaa
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: a4c51710
|
||||||
|
- Summary: aaa
|
||||||
|
- Files: wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/templates/voidship-sheet.hbs, wg-voidships-builder/wg-voidships-builder.zip
|
||||||
|
|
||||||
|
### 111
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 1abe31b3
|
||||||
|
- Summary: 111
|
||||||
|
- Files: foundry/wg-voidship-hulls copy.db, wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/packs/wg-voidship-items.db, wg-voidships-builder/sample_universe.json, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/scripts/voidships.js (+1 more)
|
||||||
|
|
||||||
|
### asd
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: ddae4ad4
|
||||||
|
- Summary: asd
|
||||||
|
- Files: wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/scripts/voidships.js
|
||||||
|
|
||||||
|
### test
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 5186b264
|
||||||
|
- Summary: test
|
||||||
|
- Files: wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/templates/voidship-sheet.hbs
|
||||||
|
|
||||||
|
### 1
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: e84c0127
|
||||||
|
- Summary: 1
|
||||||
|
- Files: wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/scripts/voidships.js, wg-voidships-builder/templates/voidship-sheet.hbs
|
||||||
|
|
||||||
|
### aaa
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 5892e614
|
||||||
|
- Summary: aaa
|
||||||
|
- Files: wg-voidships-builder/module.json, wg-voidships-builder/scripts/voidship-actor.js, wg-voidships-builder/templates/voidship-sheet.hbs
|
||||||
|
|
||||||
|
### 11
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: e8ad5478
|
||||||
|
- Summary: 11
|
||||||
|
- Files: foundry.zip, wg-greyknights.0.6.4.9.zip, wg-voidships-builder.1.4.8.zip
|
||||||
|
|
||||||
|
### ss
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 89791655
|
||||||
|
- Summary: ss
|
||||||
|
- Files: foundry/Aegis Power Armour.json, foundry/Aegis Terminator Armour.json, foundry/Grey Knight Archetype.json, foundry/fvtt-Actor-aquila-lander-Qz0AG5vJU9HrYtxk.json, foundry/fvtt-Actor-cobra-class-destroyer-(renegade)-jPYz63kXVprYFXMi.json, foundry/fvtt-Actor-cobra-class-destroyer-b518e981d69324aa.json (+26 more)
|
||||||
|
|
||||||
|
### More context files
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 9c3d7350
|
||||||
|
- Summary: More context files
|
||||||
|
- Files: wg-greyknights/foundry/Aegis Power Armour.json, wg-greyknights/foundry/Aegis Terminator Armour.json, wg-greyknights/foundry/Grey Knight Archetype.json, wg-greyknights/foundry/fvtt-Actor-aquila-lander-Qz0AG5vJU9HrYtxk.json, wg-greyknights/foundry/fvtt-Actor-cobra-class-destroyer-(renegade)-jPYz63kXVprYFXMi.json, wg-greyknights/foundry/fvtt-Actor-cobra-class-destroyer-b518e981d69324aa.json (+13 more)
|
||||||
|
|
||||||
|
### Final final final
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: d977cff4
|
||||||
|
- Summary: Final final final
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/ui-gkpanel.js
|
||||||
|
|
||||||
|
### Final
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 56ada5e1
|
||||||
|
- Summary: Final
|
||||||
|
- Files: wg-greyknights/LICENSE, wg-greyknights/README.txt, wg-greyknights/icons/placeholder.txt, wg-greyknights/manifest.json, wg-greyknights/packs/daemonhunters.db, wg-greyknights/packs/gk-items-060.db (+4 more)
|
||||||
|
|
||||||
|
### cleanup
|
||||||
|
- Date: 2025-09-18
|
||||||
|
- Commit: 7c35c1bb
|
||||||
|
- Summary: cleanup
|
||||||
|
- Files: wg-greyknights/content/Daemonhunters/Abilities/Aegis of Titan.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight (General).json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Justicar.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Paladin.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Purgation Marine.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Purifier.json (+20 more)
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.8.zip do wg-greyknights (2025-08-25T13:36:01Z)
|
||||||
|
- Date: 2025-08-25
|
||||||
|
- Commit: b2c47b68
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.8.zip do wg-greyknights (2025-08-25T13:36:01Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.5.zip do wg-greyknights (2025-08-23T20:41:27Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: fbbc5eec
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.5.zip do wg-greyknights (2025-08-23T20:41:27Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.4.zip do wg-greyknights (2025-08-23T20:15:27Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: 1d346db6
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.4.zip do wg-greyknights (2025-08-23T20:15:27Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.3.zip do wg-greyknights (2025-08-23T19:48:54Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: 0e417ca6
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.3.zip do wg-greyknights (2025-08-23T19:48:54Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.2.zip do wg-greyknights (2025-08-23T19:35:08Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: 47a0250f
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.2.zip do wg-greyknights (2025-08-23T19:35:08Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4.1-patch.zip do wg-greyknights (2025-08-23T19:02:02Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: f4074855
|
||||||
|
- Summary: Import gk-greyknight-0.6.4.1-patch.zip do wg-greyknights (2025-08-23T19:02:02Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.4-patch.zip do wg-greyknights (2025-08-23T19:01:42Z)
|
||||||
|
- Date: 2025-08-23
|
||||||
|
- Commit: 4dbce12b
|
||||||
|
- Summary: Import gk-greyknight-0.6.4-patch.zip do wg-greyknights (2025-08-23T19:01:42Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import 0.6.1.stable.gk.zip do wg-greyknights (2025-08-22T17:44:46Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: e29930d0
|
||||||
|
- Summary: Import 0.6.1.stable.gk.zip do wg-greyknights (2025-08-22T17:44:46Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/helpers.js
|
||||||
|
|
||||||
|
### Import wng-daemonhunters-0.6.4.2.zip do wg-greyknights (2025-08-22T17:26:07Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 45b2d0d3
|
||||||
|
- Summary: Import wng-daemonhunters-0.6.4.2.zip do wg-greyknights (2025-08-22T17:26:07Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/daemonhunters.db, wg-greyknights/scripts/auto-gk-archetype.js
|
||||||
|
|
||||||
|
### Import wng-daemonhunters-0.6.4.1.zip do wg-greyknights (2025-08-22T17:07:40Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: e164cb6e
|
||||||
|
- Summary: Import wng-daemonhunters-0.6.4.1.zip do wg-greyknights (2025-08-22T17:07:40Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/daemonhunters.db, wg-greyknights/scripts/auto-gk-archetype.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.3.zip do wg-greyknights (2025-08-22T12:08:07Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: fe70f879
|
||||||
|
- Summary: Import gk-greyknight-0.6.3.zip do wg-greyknights (2025-08-22T12:08:07Z)
|
||||||
|
- Files: wg-greyknights/module.json, wg-greyknights/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.1.zip do wg-greyknights (2025-08-22T11:40:25Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: bf3fe1e7
|
||||||
|
- Summary: Import gk-greyknight-0.6.1.zip do wg-greyknights (2025-08-22T11:40:25Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.6.0.zip do wg-greyknights (2025-08-22T11:09:50Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 4408ca89
|
||||||
|
- Summary: Import gk-greyknight-0.6.0.zip do wg-greyknights (2025-08-22T11:09:50Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/module.json, wg-greyknights/packs/gk-items-060.db, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.5.3.zip do wg-greyknights (2025-08-22T10:31:14Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 73b70ca4
|
||||||
|
- Summary: Import gk-greyknight-0.5.3.zip do wg-greyknights (2025-08-22T10:31:14Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-last_stable.zip do wg-greyknights (2025-08-22T10:21:33Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 2e44ddf7
|
||||||
|
- Summary: Import gk-greyknight-last_stable.zip do wg-greyknights (2025-08-22T10:21:33Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.5.1-patch/packs/gk-items.db, wg-greyknights/module.json, wg-greyknights/packs/gk-items.db, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.5.2-patch.zip do wg-greyknights (2025-08-22T10:04:48Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 235f91f0
|
||||||
|
- Summary: Import gk-greyknight-0.5.2-patch.zip do wg-greyknights (2025-08-22T10:04:48Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.7-patch/module.json.patch.json, wg-greyknights/gk-greyknight-0.5.2-patch/module.json.patch.json, wg-greyknights/gk-greyknight-0.5.2-patch/packs/gk-items-051.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.5.1-patch.zip do wg-greyknights (2025-08-22T09:46:13Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: d7bb2559
|
||||||
|
- Summary: Import gk-greyknight-0.5.1-patch.zip do wg-greyknights (2025-08-22T09:46:13Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.5.0-patch/packs/gk-items.db, wg-greyknights/gk-greyknight-0.5.1-patch/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.5.0-patch.zip do wg-greyknights (2025-08-22T00:06:01Z)
|
||||||
|
- Date: 2025-08-22
|
||||||
|
- Commit: 06ae94b8
|
||||||
|
- Summary: Import gk-greyknight-0.5.0-patch.zip do wg-greyknights (2025-08-22T00:06:01Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.5.0-patch/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.9-patch.zip do wg-greyknights (2025-08-21T23:12:29Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 8fe7cf51
|
||||||
|
- Summary: Import gk-greyknight-0.4.9-patch.zip do wg-greyknights (2025-08-21T23:12:29Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.9-patch/packs/gk-items.db, wg-greyknights/gk-greyknight-0.4.9-patch/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.8-patch.zip do wg-greyknights (2025-08-21T22:49:35Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 2452cb60
|
||||||
|
- Summary: Import gk-greyknight-0.4.8-patch.zip do wg-greyknights (2025-08-21T22:49:35Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.8-patch/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.7-patch.zip do wg-greyknights (2025-08-21T22:41:13Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: f7b7e00c
|
||||||
|
- Summary: Import gk-greyknight-0.4.7-patch.zip do wg-greyknights (2025-08-21T22:41:13Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.7-patch/module.json.patch.json, wg-greyknights/gk-greyknight-0.4.7-patch/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.6-patch.zip do wg-greyknights (2025-08-21T22:23:55Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 2111d117
|
||||||
|
- Summary: Import gk-greyknight-0.4.6-patch.zip do wg-greyknights (2025-08-21T22:23:55Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.6-patch/packs/gk-items.db, wg-greyknights/gk-greyknight-0.4.6-patch/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.5-patch.zip do wg-greyknights (2025-08-21T22:05:46Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 5fcdc8d2
|
||||||
|
- Summary: Import gk-greyknight-0.4.5-patch.zip do wg-greyknights (2025-08-21T22:05:46Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.5-patch/packs/gk-items.db, wg-greyknights/gk-greyknight-0.4.5-patch/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.4-patch.zip do wg-greyknights (2025-08-21T21:44:57Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 305edfa7
|
||||||
|
- Summary: Import gk-greyknight-0.4.4-patch.zip do wg-greyknights (2025-08-21T21:44:57Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.2-patch/scripts/auto-loadout.js, wg-greyknights/gk-greyknight-0.4.4-patch/module.json.patch.json, wg-greyknights/gk-greyknight-0.4.4-patch/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.3-patch.zip do wg-greyknights (2025-08-21T21:34:22Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 0ab17ffe
|
||||||
|
- Summary: Import gk-greyknight-0.4.3-patch.zip do wg-greyknights (2025-08-21T21:34:22Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.3-patch/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.2-patch.zip do wg-greyknights (2025-08-21T21:19:36Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: cf7c4696
|
||||||
|
- Summary: Import gk-greyknight-0.4.2-patch.zip do wg-greyknights (2025-08-21T21:19:36Z)
|
||||||
|
- Files: wg-greyknights/gk-greyknight-0.4.2-patch/module.json.patch.json, wg-greyknights/gk-greyknight-0.4.2-patch/packs/gk-items.db, wg-greyknights/gk-greyknight-0.4.2-patch/scripts/auto-loadout.js, wg-greyknights/scripts/auto-loadout.js
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.4.1.zip do wg-greyknights (2025-08-21T20:56:51Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: da34a795
|
||||||
|
- Summary: Import gk-greyknight-0.4.1.zip do wg-greyknights (2025-08-21T20:56:51Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/module.json
|
||||||
|
|
||||||
|
### Import gk-greyknight-compendium-0.4.0.zip do wg-greyknights (2025-08-21T20:43:39Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: b3017588
|
||||||
|
- Summary: Import gk-greyknight-compendium-0.4.0.zip do wg-greyknights (2025-08-21T20:43:39Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/docs/README.txt, wg-greyknights/module.json, wg-greyknights/packs/gk-items.db
|
||||||
|
|
||||||
|
### Import wrath-and-glory-gk-0.3.0.zip do wg-greyknights (2025-08-21T20:13:12Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 10c76306
|
||||||
|
- Summary: Import wrath-and-glory-gk-0.3.0.zip do wg-greyknights (2025-08-21T20:13:12Z)
|
||||||
|
- Files: wg-greyknights/content/Daemonhunters/Abilities/Aegis of Titan.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight (General).json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Justicar.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Paladin.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Purgation Marine.json, wg-greyknights/content/Daemonhunters/Archetypes/Grey Knight Purifier.json (+13 more)
|
||||||
|
|
||||||
|
### Import wrath-and-glory-gk-0.2.1.zip do wg-greyknights (2025-08-21T20:13:04Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: cbbdb35f
|
||||||
|
- Summary: Import wrath-and-glory-gk-0.2.1.zip do wg-greyknights (2025-08-21T20:13:04Z)
|
||||||
|
- Files: wg-greyknights/docs/README.txt, wg-greyknights/manifest.json, wg-greyknights/module.json
|
||||||
|
|
||||||
|
### Import wrath-and-glory-gk-0.2.0.zip do wg-greyknights (2025-08-21T20:12:55Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: b4ee703f
|
||||||
|
- Summary: Import wrath-and-glory-gk-0.2.0.zip do wg-greyknights (2025-08-21T20:12:55Z)
|
||||||
|
- Files: wg-greyknights/README.txt, wg-greyknights/docs/README.txt, wg-greyknights/foundry/items/Aegis Power Armour.json, wg-greyknights/foundry/items/Aegis Terminator Armour.json, wg-greyknights/foundry/items/Aegis of Titan.json, wg-greyknights/foundry/items/Grey Knight Archetype.json (+8 more)
|
||||||
|
|
||||||
|
### Import gk-greyknight-0.1.0.zip do wg-greyknights (2025-08-21T19:51:05Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: 24ad39dc
|
||||||
|
- Summary: Import gk-greyknight-0.1.0.zip do wg-greyknights (2025-08-21T19:51:05Z)
|
||||||
|
- Files: wg-greyknights/LICENSE, wg-greyknights/README.txt, wg-greyknights/foundry/actors/gk-incognito.json, wg-greyknights/foundry/actors/gk-power-armour.json, wg-greyknights/foundry/actors/gk-terminator.json, wg-greyknights/foundry/fvtt-Item-ability-aegis-of-titan_v5.json (+10 more)
|
||||||
|
|
||||||
|
### Import GK_Foundry_Macros_Fix_v5.zip do wg-greyknights (2025-08-21T19:30:33Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: dc829069
|
||||||
|
- Summary: Import GK_Foundry_Macros_Fix_v5.zip do wg-greyknights (2025-08-21T19:30:33Z)
|
||||||
|
- Files: wg-greyknights/macro/macro-gk-auto-loadout-on-archetype.js, wg-greyknights/macros/macro-gk-auto-loadout-on-archetype.js, wg-greyknights/macros/macro-gk-glow-toggle.js, wg-greyknights/macros/macro-gk-import-world-items-FIXED.js, wg-greyknights/macros/macro-gk-loadout-manager.js
|
||||||
|
|
||||||
|
### Import GK_Foundry_GreyKnight_v5.zip do wg-greyknights (2025-08-21T19:02:44Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: edb3f0df
|
||||||
|
- Summary: Import GK_Foundry_GreyKnight_v5.zip do wg-greyknights (2025-08-21T19:02:44Z)
|
||||||
|
- Files: wg-greyknights/foundry/fvtt-Item-ability-aegis-of-titan_v5.json, wg-greyknights/foundry/fvtt-Item-aegis-power-armour_v2.json, wg-greyknights/foundry/fvtt-Item-archetype-grey-knight_v5.json, wg-greyknights/foundry/fvtt-Item-gk-armour-integrated-storm-bolter_v2.json, wg-greyknights/foundry/fvtt-Item-nemesis-force-halberd_v4.json, wg-greyknights/foundry/fvtt-Item-terminator-aegis-gk_v4.json (+4 more)
|
||||||
|
|
||||||
|
### Import Grey_Knight_WrathAndGlory_FoundryPack.zip do wg-greyknights (2025-08-21T17:10:55Z)
|
||||||
|
- Date: 2025-08-21
|
||||||
|
- Commit: c725793b
|
||||||
|
- Summary: Import Grey_Knight_WrathAndGlory_FoundryPack.zip do wg-greyknights (2025-08-21T17:10:55Z)
|
||||||
|
- Files: wg-greyknights/foundry/fvtt-Actor-grey-knight-FRESH.json, wg-greyknights/foundry/fvtt-Item-ability-aegis-of-titan.json, wg-greyknights/foundry/fvtt-Item-aegis-psychic-hood-gk_v2.json, wg-greyknights/foundry/fvtt-Item-archetype-grey-knight_v2.json, wg-greyknights/foundry/fvtt-Item-nemesis-force-halberd.json, wg-greyknights/foundry/fvtt-Item-pilgrim-astartes-plate.json (+2 more)
|
||||||
|
|
||||||
|
## RELEASE 0.1
|
||||||
|
### a
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: ddb82921
|
||||||
|
- Tags: 0.1
|
||||||
|
- Summary: a
|
||||||
|
- Files: import.py
|
||||||
|
|
||||||
|
### aa
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: 541ef488
|
||||||
|
- Summary: aa
|
||||||
|
- Files: import.py
|
||||||
|
|
||||||
|
### aa
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: e256c731
|
||||||
|
- Summary: aa
|
||||||
|
- Files: import.py, wg-greyknights/module.json
|
||||||
|
|
||||||
|
### Rename
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: e284eceb
|
||||||
|
- Summary: Rename
|
||||||
|
- Files: wg-voidships-builder/LICENSE, wg-voidships-builder/journal/README.md, wg-voidships-builder/module.json, wg-voidships-builder/packs/wg-voidship-archeotech.db, wg-voidships-builder/packs/wg-voidship-hulls.db, wg-voidships-builder/packs/wg-voidship-items.db (+5 more)
|
||||||
|
|
||||||
|
### Done
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: 355e4ab7
|
||||||
|
- Summary: Done
|
||||||
|
- Files: wg-voidships-foundry/packs/wg-voidship-items.db, wg-voidships-foundry/packs/wg-voidship-macros.db, wg-voidships-foundry/packs/wg-voidships-items.db
|
||||||
|
|
||||||
|
### asss
|
||||||
|
- Date: 2025-09-17
|
||||||
|
- Commit: 06297a9e
|
||||||
|
- Summary: asss
|
||||||
|
- Files: wg-voidships-foundry/packs/wg-voidship-items.db, wg-voidships-foundry/packs/wg-voidship-macros.db
|
||||||
|
|
||||||
|
### 12
|
||||||
|
- Date: 2025-09-16
|
||||||
|
- Commit: 1c80b743
|
||||||
|
- Summary: 12
|
||||||
|
- Files: wg-voidships-foundry/packs/wg-voidship-archeotech.db, wg-voidships-foundry/packs/wg-voidship-items.db, wg-voidships-foundry/packs/wg-voidship-macros.db, wg-voidships-foundry/packs/wg-voidship-xenos.db, wg-voidships-foundry/packs/wg-voidships-macros.db
|
||||||
|
|
||||||
|
### dd
|
||||||
|
- Date: 2025-09-16
|
||||||
|
- Commit: 22e4553c
|
||||||
|
- Summary: dd
|
||||||
|
- Files: wg-voidships-foundry/journal/README.md, wg-voidships-foundry/packs/wg-voidship-hulls.db, wg-voidships-foundry/packs/wg-voidship-items.db, wg-voidships-foundry/packs/wg-voidship-macros.db, wg-voidships-foundry/packs/wg-voidship-xenos.db, wg-voidships-foundry/packs/wg-voidship-xewnos.db (+1 more)
|
||||||
|
|
||||||
|
### 11
|
||||||
|
- Date: 2025-09-16
|
||||||
|
- Commit: e34f3a22
|
||||||
|
- Summary: 11
|
||||||
|
- Files: wg-voidships-foundry/module.json, wg-voidships-foundry/packs/wg-voidship-archeotech.db, wg-voidships-foundry/packs/wg-voidship-xewnos.db, wg-voidships-foundry/packs/wg-voidships-items.db, wg-voidships-foundry/patch_hulls.py
|
||||||
|
|
||||||
|
### ASaa
|
||||||
|
- Date: 2025-09-16
|
||||||
|
- Commit: 5511acfb
|
||||||
|
- Summary: ASaa
|
||||||
|
- Files: sound.py, wg-voidships-foundry.zip, wg-voidships-foundry/module.json, wg-voidships-foundry/packs/wg-voidship-hulls.db, wg-voidships-foundry/packs/wg-voidships-actors.db, wg-voidships-foundry/packs/wg-voidships-items.db (+3 more)
|
||||||
|
|
||||||
|
### sss
|
||||||
|
- Date: 2025-08-29
|
||||||
|
- Commit: db350588
|
||||||
|
- Summary: sss
|
||||||
|
- Files: wg-voidships-foundry/journal/README.md, wg-voidships-foundry/module.json, wg-voidships-foundry/packs/wg-voidships-items.db
|
||||||
|
|
||||||
|
### 0.1
|
||||||
|
- Date: 2025-08-29
|
||||||
|
- Commit: 987eef4f
|
||||||
|
- Summary: 0.1
|
||||||
|
- Files: wg-voidships-foundry/LICENSE, wg-voidships-foundry/journal/README.md, wg-voidships-foundry/module.json, wg-voidships-foundry/packs/wg-voidships-actors.db, wg-voidships-foundry/packs/wg-voidships-macros.db, wg-voidships-foundry/packs/wg-voidships-tables.db (+1 more)
|
||||||
|
|
||||||
|
### Module import
|
||||||
|
- Date: 2025-08-27
|
||||||
|
- Commit: 7465a79a
|
||||||
|
- Summary: Module import
|
||||||
|
- Files: wg-voidships-journal.1.0.zip, wg-voidships-journal/LICENSE, wg-voidships-journal/README.md, wg-voidships-journal/module.json, wg-voidships-journal/packs/wg-voidships-docs.db, wg-voidships.1.0.1.zip (+3 more)
|
||||||
|
|
||||||
|
### Voidships - Ship - part 2
|
||||||
|
- Date: 2025-08-27
|
||||||
|
- Commit: 18a3ef3c
|
||||||
|
- Summary: Voidships - Ship - part 2
|
||||||
|
- Files: wg-voidships/voidship-docs-journal-import.json, wg-voidships_doc/wg-voidhsips_of_gilead_doc/Voidships_WG_ships2.html
|
||||||
|
|
||||||
|
### fix
|
||||||
|
- Date: 2025-08-27
|
||||||
|
- Commit: 769d568d
|
||||||
|
- Summary: fix
|
||||||
|
- Files: wg-voidships_doc/wg-voidhsips_of_gilead_doc/Voidships_WG_ships.html
|
||||||
|
|
||||||
|
### V 1.0.1 release candidate start of v 1.1.0 development
|
||||||
|
- Date: 2025-08-27
|
||||||
|
- Commit: 62989221
|
||||||
|
- Summary: V 1.0.1 release candidate start of v 1.1.0 development
|
||||||
|
- Files: wg-voidships.1.0.1.zip, wg-voidships_doc/wg-voidhsips_of_gilead_doc/ships.html
|
||||||
|
|
||||||
|
## RELEASE 1.0.2-
|
||||||
|
### v 1.0.2
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: b4728cf1
|
||||||
|
- Tags: 1.0.2-
|
||||||
|
- Summary: v 1.0.2
|
||||||
|
- Files: wg-voidships/LICENSE.txt, wg-voidships/README.md, wg-voidships/module.json, wg-voidships/packs/campaign-silica-animus.db, wg-voidships/packs/ships-of-gilead-v3.db, wg-voidships/packs/voidships-catalogue-v3.db (+7 more)
|
||||||
|
|
||||||
|
### fx - vs 1.0.1
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: f3f689b0
|
||||||
|
- Summary: fx - vs 1.0.1
|
||||||
|
- Files: wg-voidships_doc/Voidships_WG_Catalogue.html, wg-voidships_doc/Voidships_WG_Catalogue_critical_hits.html, wg-voidships_doc/Voidships_WG_Construction.html, wg-voidships_doc/Voidships_WG_Rules.html
|
||||||
|
|
||||||
|
## RELEASE voidships-1.0
|
||||||
|
### Voidships rules v.1.0
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: 4ed12c28
|
||||||
|
- Tags: voidships-1.0
|
||||||
|
- Summary: Voidships rules v.1.0
|
||||||
|
- Files: wg-voidships_doc/Voidships_WG_Catalogue_v3.html, wg-voidships_doc/Voidships_WG_Construction_RT_v3.html, wg-voidships_doc/Voidships_WG_Rules_v3.html
|
||||||
|
|
||||||
|
### v2
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: 1fbe8bb7
|
||||||
|
- Summary: v2
|
||||||
|
- Files: wg-voidships_doc/Voidships_WG_Rules_v3.html
|
||||||
|
|
||||||
|
### v2
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: 5af5f455
|
||||||
|
- Summary: v2
|
||||||
|
- Files: wg-voidships_doc/Voidships_WG_Catalogue_v3.html, wg-voidships_doc/Voidships_WG_Construction_RT_v3.html, wg-voidships_doc/Voidships_WG_Rules_v3.html
|
||||||
|
|
||||||
|
### Initial commit voidships
|
||||||
|
- Date: 2025-08-26
|
||||||
|
- Commit: 0368c130
|
||||||
|
- Summary: Initial commit voidships
|
||||||
|
- Files: wg-voidships/LICENSE.txt, wg-voidships/README.md, wg-voidships/module.json, wg-voidships/packs/campaign-silica-animus.db, wg-voidships/packs/ships-of-gilead-v3.db, wg-voidships/packs/voidships-catalogue-v3.db (+6 more)
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
manage_releases.py
|
||||||
|
|
||||||
|
Automates synchronization of Git tags with GitHub releases. On each run it:
|
||||||
|
* Verifies the repository is clean and the current branch is fully pushed.
|
||||||
|
* Ensures HEAD is tagged by comparing the root version.nfo with existing
|
||||||
|
release tags and bumping/committing when required.
|
||||||
|
* Iterates over every tag in the repository, creating GitHub releases for any
|
||||||
|
missing entries and attaching per-directory ZIP archives (excluding
|
||||||
|
version.nfo files) that reflect the repository state for that tag.
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- gh CLI (https://cli.github.com/)
|
||||||
|
- git available in PATH
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Iterable, List, Optional, Sequence, Tuple
|
||||||
|
from zipfile import ZIP_DEFLATED, ZipFile
|
||||||
|
|
||||||
|
|
||||||
|
def sh(args: Sequence[str], cwd: Path, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
"""Run a subprocess and return the completed process."""
|
||||||
|
result = subprocess.run(
|
||||||
|
list(args),
|
||||||
|
cwd=str(cwd),
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if check and result.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Command failed ({' '.join(args)}):\n"
|
||||||
|
f"{result.stdout}{result.stderr}"
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description="Synchronize Git tags with GitHub releases.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--repo",
|
||||||
|
default=".",
|
||||||
|
help="Ścieżka do repozytorium git (domyślnie: bieżący katalog).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--remote",
|
||||||
|
default="origin",
|
||||||
|
help="Nazwa zdalnego repozytorium do pushowania commitów oraz tagów (domyślnie: origin).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir",
|
||||||
|
default="Release packs",
|
||||||
|
help="Katalog wyjściowy, w którym powstaną ZIP-y uploadowane do release (domyślnie: 'Release packs').",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--notes-template",
|
||||||
|
default="Automated release for {tag}",
|
||||||
|
help="Szablon notatek release (dostępne pola: {tag}).",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Tylko plan – bez commitów, tagowania i publikacji release.",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def read_text_if_exists(path: Path) -> Optional[str]:
|
||||||
|
if not path.is_file():
|
||||||
|
return None
|
||||||
|
return path.read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version(version: str) -> Optional[Tuple[int, ...]]:
|
||||||
|
if not version:
|
||||||
|
return None
|
||||||
|
if not re.fullmatch(r"\d+(?:\.\d+)*", version):
|
||||||
|
return None
|
||||||
|
return tuple(int(part) for part in version.split("."))
|
||||||
|
|
||||||
|
|
||||||
|
def bump_version(version: str) -> str:
|
||||||
|
parts = parse_version(version)
|
||||||
|
if parts is None:
|
||||||
|
# fallback: append .1 to non-numeric strings
|
||||||
|
return f"{version}.1"
|
||||||
|
bumped = list(parts)
|
||||||
|
bumped[-1] += 1
|
||||||
|
return ".".join(str(piece) for piece in bumped)
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_component(value: str) -> str:
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return "unknown"
|
||||||
|
return re.sub(r"[\\s/\\\\]+", "-", value)
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseManager:
|
||||||
|
def __init__(self, args: argparse.Namespace):
|
||||||
|
self.args = args
|
||||||
|
self.repo = Path(args.repo).resolve()
|
||||||
|
self.output_dir = (self.repo / args.output_dir).resolve()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------ helpers
|
||||||
|
def git(self, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
||||||
|
return sh(args, cwd=self.repo, check=check)
|
||||||
|
|
||||||
|
def ensure_tooling(self) -> None:
|
||||||
|
sh(["gh", "--version"], cwd=self.repo, check=True)
|
||||||
|
|
||||||
|
def ensure_repo(self) -> None:
|
||||||
|
if not (self.repo / ".git").exists():
|
||||||
|
raise RuntimeError(f"{self.repo} nie jest repozytorium git.")
|
||||||
|
|
||||||
|
def ensure_clean_state(self) -> None:
|
||||||
|
status = self.git("git", "status", "--porcelain", check=True).stdout.strip()
|
||||||
|
if status:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Wykryto niezakomitowane zmiany. Ukończ commit lub stash przed publikacją release.\n"
|
||||||
|
"Release podpisany z nieczystego drzewa może wymagać resetu HEAD do taga."
|
||||||
|
)
|
||||||
|
|
||||||
|
upstream = self.git("git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}", check=False)
|
||||||
|
if upstream.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Brak skonfigurowanego upstreamu dla bieżącej gałęzi – nie można zweryfikować, "
|
||||||
|
"czy wszystkie commity zostały wypchnięte."
|
||||||
|
)
|
||||||
|
ahead = self.git("git", "rev-list", "--count", "@{u}..HEAD", check=True).stdout.strip()
|
||||||
|
if ahead and int(ahead) > 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Bieżąca gałąź zawiera niewypchnięte commity. Wypchnij zmiany zanim utworzysz release."
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_head_tag(self) -> Optional[str]:
|
||||||
|
result = self.git("git", "describe", "--tags", "--exact-match", "HEAD", check=False)
|
||||||
|
tag = result.stdout.strip()
|
||||||
|
if result.returncode == 0 and tag:
|
||||||
|
return tag
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_tags(self) -> List[str]:
|
||||||
|
tags = self.git("git", "tag", "--sort=creatordate", check=True).stdout.splitlines()
|
||||||
|
return [tag.strip() for tag in tags if tag.strip()]
|
||||||
|
|
||||||
|
def list_version_tags(self) -> List[str]:
|
||||||
|
tags = []
|
||||||
|
for tag in self.list_tags():
|
||||||
|
if parse_version(tag):
|
||||||
|
tags.append(tag)
|
||||||
|
return tags
|
||||||
|
|
||||||
|
def read_root_version(self) -> Optional[str]:
|
||||||
|
return read_text_if_exists(self.repo / "version.nfo")
|
||||||
|
|
||||||
|
def write_root_version(self, version: str) -> None:
|
||||||
|
target = self.repo / "version.nfo"
|
||||||
|
target.write_text(f"{version}\n", encoding="utf-8")
|
||||||
|
|
||||||
|
def commit_root_version(self, version: str) -> None:
|
||||||
|
if self.args.dry_run:
|
||||||
|
print(f"[DRY-RUN] git add version.nfo && git commit -m 'release: bump root to v{version}'")
|
||||||
|
return
|
||||||
|
self.write_root_version(version)
|
||||||
|
self.git("git", "add", "version.nfo", check=True)
|
||||||
|
self.git("git", "commit", "-m", f"release: bump root to v{version}", check=True)
|
||||||
|
self.git("git", "push", self.args.remote, check=True)
|
||||||
|
|
||||||
|
def tag_exists(self, tag: str) -> bool:
|
||||||
|
result = self.git("git", "rev-parse", "-q", "--verify", f"refs/tags/{tag}", check=False)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
def create_tag(self, tag: str, message: str) -> None:
|
||||||
|
if self.tag_exists(tag):
|
||||||
|
print(f"[INFO] Tag {tag} już istnieje – pomijam tworzenie.")
|
||||||
|
return
|
||||||
|
if self.args.dry_run:
|
||||||
|
print(f"[DRY-RUN] git tag -a {tag} -m \"{message}\"")
|
||||||
|
return
|
||||||
|
self.git("git", "tag", "-a", tag, "-m", message, check=True)
|
||||||
|
self.git("git", "push", self.args.remote, tag, check=True)
|
||||||
|
|
||||||
|
def ensure_head_tagged(self) -> str:
|
||||||
|
head_tag = self.get_head_tag()
|
||||||
|
if head_tag:
|
||||||
|
return head_tag
|
||||||
|
|
||||||
|
root_version = self.read_root_version()
|
||||||
|
root_tuple = parse_version(root_version) if root_version else None
|
||||||
|
|
||||||
|
version_tags = self.list_version_tags()
|
||||||
|
latest_version = version_tags[-1] if version_tags else None
|
||||||
|
latest_tuple = parse_version(latest_version) if latest_version else None
|
||||||
|
|
||||||
|
if root_version and (root_tuple and (latest_tuple is None or root_tuple > latest_tuple)):
|
||||||
|
new_version = root_version
|
||||||
|
else:
|
||||||
|
base_version = latest_version or root_version or "0.0"
|
||||||
|
new_version = bump_version(base_version)
|
||||||
|
if not self.args.dry_run:
|
||||||
|
print(f"[INFO] Bump root version.nfo -> {new_version}")
|
||||||
|
self.commit_root_version(new_version)
|
||||||
|
|
||||||
|
tag_message = f"release {new_version}"
|
||||||
|
self.create_tag(new_version, tag_message)
|
||||||
|
return new_version
|
||||||
|
|
||||||
|
def gather_directories_for_tag(self, tag: str) -> List[str]:
|
||||||
|
result = self.git("git", "ls-tree", "-d", "--name-only", tag, check=True)
|
||||||
|
candidates = []
|
||||||
|
for line in result.stdout.splitlines():
|
||||||
|
name = line.strip()
|
||||||
|
if not name:
|
||||||
|
continue
|
||||||
|
candidates.append(name)
|
||||||
|
return candidates
|
||||||
|
|
||||||
|
def version_for_directory(self, tag: str, directory: str) -> str:
|
||||||
|
show = self.git("git", "show", f"{tag}:{directory}/version.nfo", check=False)
|
||||||
|
if show.returncode == 0:
|
||||||
|
version = show.stdout.strip()
|
||||||
|
if version:
|
||||||
|
return version
|
||||||
|
return tag
|
||||||
|
|
||||||
|
def build_assets_for_tag(self, tag: str) -> List[Path]:
|
||||||
|
assets: List[Path] = []
|
||||||
|
directories = self.gather_directories_for_tag(tag)
|
||||||
|
if not directories:
|
||||||
|
print(f"[WARN] Tag {tag} nie zawiera katalogów do zzipowania.")
|
||||||
|
return assets
|
||||||
|
|
||||||
|
tag_output_dir = self.output_dir / sanitize_component(tag)
|
||||||
|
if not self.args.dry_run:
|
||||||
|
if tag_output_dir.exists():
|
||||||
|
shutil.rmtree(tag_output_dir)
|
||||||
|
tag_output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for directory in directories:
|
||||||
|
version = self.version_for_directory(tag, directory)
|
||||||
|
safe_version = sanitize_component(version)
|
||||||
|
safe_dir = sanitize_component(directory)
|
||||||
|
zip_name = f"{safe_dir}-v{safe_version}.zip"
|
||||||
|
zip_path = tag_output_dir / zip_name
|
||||||
|
|
||||||
|
if self.args.dry_run:
|
||||||
|
print(f"[DRY-RUN] Przygotuj ZIP {zip_path} z taga {tag}:{directory} (pomijając version.nfo)")
|
||||||
|
assets.append(zip_path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Re-run capturing binary properly.
|
||||||
|
archive_proc = subprocess.run(
|
||||||
|
["git", "archive", "--format=tar", tag, directory],
|
||||||
|
cwd=str(self.repo),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
if archive_proc.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"git archive nie powiódł się dla {tag}:{directory}:\n"
|
||||||
|
f"{archive_proc.stderr.decode('utf-8', errors='ignore')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_path = Path(tmp_dir)
|
||||||
|
with tarfile.open(fileobj=io.BytesIO(archive_proc.stdout)) as tar:
|
||||||
|
tar.extractall(tmp_path)
|
||||||
|
|
||||||
|
extracted_root = tmp_path / directory
|
||||||
|
for extra in extracted_root.rglob("version.nfo"):
|
||||||
|
if extra.is_file():
|
||||||
|
extra.unlink()
|
||||||
|
|
||||||
|
with ZipFile(zip_path, "w", ZIP_DEFLATED) as zipf:
|
||||||
|
for root, _, files in os.walk(extracted_root):
|
||||||
|
for filename in files:
|
||||||
|
file_path = Path(root) / filename
|
||||||
|
rel_path = file_path.relative_to(tmp_path)
|
||||||
|
zipf.write(file_path, rel_path.as_posix())
|
||||||
|
|
||||||
|
assets.append(zip_path)
|
||||||
|
|
||||||
|
return assets
|
||||||
|
|
||||||
|
def release_exists(self, tag: str) -> bool:
|
||||||
|
result = self.git("gh", "release", "view", tag, check=False)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
def create_release(self, tag: str, assets: List[Path]) -> None:
|
||||||
|
notes = self.args.notes_template.format(tag=tag)
|
||||||
|
wrapped = "\n".join(textwrap.wrap(notes, width=100))
|
||||||
|
|
||||||
|
if self.args.dry_run:
|
||||||
|
print(f"[DRY-RUN] gh release create {tag} {' '.join(str(a) for a in assets)}")
|
||||||
|
print(f"[DRY-RUN] Notatki:\n{wrapped}")
|
||||||
|
return
|
||||||
|
|
||||||
|
cmd = ["gh", "release", "create", tag, "--title", tag, "--notes", wrapped]
|
||||||
|
cmd.extend(str(asset) for asset in assets)
|
||||||
|
result = self.git(*cmd, check=False)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Nie udało się utworzyć release {tag}:\n{result.stdout}{result.stderr}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def sync_releases(self) -> None:
|
||||||
|
tags = self.list_tags()
|
||||||
|
if not tags:
|
||||||
|
print("[INFO] Brak tagów do synchronizacji.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
if self.release_exists(tag):
|
||||||
|
print(f"[INFO] Release {tag} już istnieje – pomijam.")
|
||||||
|
continue
|
||||||
|
print(f"[INFO] Przygotowuję release dla taga {tag}...")
|
||||||
|
assets = self.build_assets_for_tag(tag)
|
||||||
|
self.create_release(tag, assets)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
self.ensure_repo()
|
||||||
|
self.ensure_tooling()
|
||||||
|
self.ensure_clean_state()
|
||||||
|
self.ensure_head_tagged()
|
||||||
|
self.sync_releases()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
manager = ReleaseManager(args)
|
||||||
|
try:
|
||||||
|
manager.run()
|
||||||
|
except RuntimeError as exc:
|
||||||
|
print(f"[BŁĄD] {exc}")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Executable
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// pack_nedb_to_leveldb.mjs
|
||||||
|
// Przepakowuje kompendia z legacy NeDB (.db, jeden JSON/linia) do formatu LevelDB
|
||||||
|
// (katalog), którego wymaga Foundry VTT v11+ (i v13/v14). Czyta listę paczek
|
||||||
|
// wprost z module.json danego modułu, więc nie trzeba niczego wpisywać ręcznie.
|
||||||
|
//
|
||||||
|
// Wymaga jednorazowo: npm i -D @foundryvtt/foundryvtt-cli (lub -g)
|
||||||
|
//
|
||||||
|
// Użycie:
|
||||||
|
// node help_scripts/pack_nedb_to_leveldb.mjs [--write] [--replace] [ścieżki/do/modułów...]
|
||||||
|
//
|
||||||
|
// (bez argumentów ścieżek) -> domyślnie 3 własne moduły w tym repo
|
||||||
|
// --write faktycznie buduje katalogi LevelDB (bez tego: tylko dry-run/plan)
|
||||||
|
// --replace po udanym zbudowaniu usuwa stary plik .db ORAZ podmienia ścieżkę
|
||||||
|
// w module.json (packs[].path: "packs/foo.db" -> "packs/foo")
|
||||||
|
//
|
||||||
|
// Bezpiecznie: domyślnie nic nie nadpisuje. LevelDB trafia obok, do packs/<name>.
|
||||||
|
// Stare .db zostają, dopóki nie podasz --replace.
|
||||||
|
|
||||||
|
import { readFile, writeFile, rm, mkdtemp, mkdir } from "node:fs/promises";
|
||||||
|
import { existsSync, statSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const REPO = path.resolve(HERE, "..");
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const WRITE = args.includes("--write");
|
||||||
|
const REPLACE = args.includes("--replace");
|
||||||
|
const moduleDirs = args.filter((a) => !a.startsWith("--"));
|
||||||
|
|
||||||
|
const DEFAULT_MODULES = [
|
||||||
|
"wg-greyknights",
|
||||||
|
"wg-voidships-builder",
|
||||||
|
"wg-voidships-journal",
|
||||||
|
].map((m) => path.join(REPO, m));
|
||||||
|
|
||||||
|
const targets = (moduleDirs.length ? moduleDirs : DEFAULT_MODULES).map((p) =>
|
||||||
|
path.resolve(p)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Typ paczki z module.json -> documentType wymagany przez CLI dla źródła NeDB.
|
||||||
|
const TYPE_OK = new Set([
|
||||||
|
"Actor", "Item", "JournalEntry", "RollTable", "Macro",
|
||||||
|
"Scene", "Playlist", "Cards", "Adventure",
|
||||||
|
]);
|
||||||
|
|
||||||
|
async function loadCli() {
|
||||||
|
try {
|
||||||
|
return await import("@foundryvtt/foundryvtt-cli");
|
||||||
|
} catch {
|
||||||
|
console.error(
|
||||||
|
"\n[!] Brak @foundryvtt/foundryvtt-cli. Zainstaluj jednorazowo:\n" +
|
||||||
|
" npm i -D @foundryvtt/foundryvtt-cli\n"
|
||||||
|
);
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processModule(cli, modDir) {
|
||||||
|
const manifestPath = path.join(modDir, "module.json");
|
||||||
|
if (!existsSync(manifestPath)) {
|
||||||
|
console.warn(`- pomijam ${modDir} (brak module.json)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
||||||
|
const packs = manifest.packs ?? [];
|
||||||
|
if (!packs.length) {
|
||||||
|
console.log(`- ${manifest.id ?? path.basename(modDir)}: brak paczek`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let manifestDirty = false;
|
||||||
|
console.log(`\n=== ${manifest.id ?? path.basename(modDir)} ===`);
|
||||||
|
|
||||||
|
for (const pack of packs) {
|
||||||
|
const rel = pack.path; // np. "packs/wg-voidship-hulls.db" LUB "packs/gk-items"
|
||||||
|
const newRel = rel.replace(/\.db$/, ""); // docelowa ścieżka LevelDB (bez .db)
|
||||||
|
const outDir = path.join(modDir, newRel);
|
||||||
|
// Plik NeDB może być tam, gdzie wskazuje manifest (….db) albo obok, gdy
|
||||||
|
// manifest podaje ścieżkę bez rozszerzenia, a na dysku i tak leży <path>.db.
|
||||||
|
const nedbAbs = rel.endsWith(".db")
|
||||||
|
? path.join(modDir, rel)
|
||||||
|
: path.join(modDir, rel + ".db");
|
||||||
|
|
||||||
|
if (existsSync(outDir) && statSync(outDir).isDirectory() && !existsSync(nedbAbs)) {
|
||||||
|
console.log(` • ${pack.name}: już LevelDB (${newRel}/) — pomijam`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!existsSync(nedbAbs) || !statSync(nedbAbs).isFile()) {
|
||||||
|
console.warn(` • ${pack.name}: nie znaleziono źródła NeDB (${path.relative(modDir, nedbAbs)}) — pomijam`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (rel === newRel) {
|
||||||
|
// manifest już wskazuje bez .db, ale plik to NeDB -> rozbieżność do naprawy
|
||||||
|
console.log(` (uwaga: module.json wskazuje "${rel}", a na dysku jest NeDB "${path.basename(nedbAbs)}")`);
|
||||||
|
}
|
||||||
|
const type = pack.type;
|
||||||
|
if (!TYPE_OK.has(type)) {
|
||||||
|
console.warn(` • ${pack.name}: nieznany type="${type}" — pomijam`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const abs = nedbAbs;
|
||||||
|
console.log(
|
||||||
|
` • ${pack.name} [${type}] ${rel} -> ${newRel}/ ${WRITE ? "" : "(dry-run)"}`
|
||||||
|
);
|
||||||
|
if (!WRITE) continue;
|
||||||
|
|
||||||
|
// 1) NeDB -> katalog źródeł (.json z poprawnymi _key)
|
||||||
|
const srcTmp = await mkdtemp(path.join(tmpdir(), "fvtt-src-"));
|
||||||
|
await cli.extractPack(abs, srcTmp, {
|
||||||
|
nedb: true,
|
||||||
|
documentType: type,
|
||||||
|
yaml: false,
|
||||||
|
log: false,
|
||||||
|
});
|
||||||
|
// 2) źródła -> LevelDB
|
||||||
|
await mkdir(outDir, { recursive: true });
|
||||||
|
await cli.compilePack(srcTmp, outDir, { yaml: false, log: false });
|
||||||
|
await rm(srcTmp, { recursive: true, force: true });
|
||||||
|
console.log(` ✓ zbudowano LevelDB: ${newRel}/`);
|
||||||
|
|
||||||
|
if (REPLACE) {
|
||||||
|
await rm(abs, { force: true });
|
||||||
|
if (pack.path !== newRel) {
|
||||||
|
pack.path = newRel;
|
||||||
|
manifestDirty = true;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
` ✓ usunięto ${path.basename(abs)}` +
|
||||||
|
(manifestDirty ? ", zaktualizowano ścieżkę w module.json" : "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifestDirty && WRITE && REPLACE) {
|
||||||
|
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cli = WRITE ? await loadCli() : null; // CLI potrzebny tylko do budowania
|
||||||
|
console.log(
|
||||||
|
WRITE
|
||||||
|
? `Przepakowuję (${REPLACE ? "z podmianą .db i module.json" : "obok, .db zostają"})`
|
||||||
|
: "DRY-RUN — plan (dodaj --write aby zbudować, --replace aby podmienić):"
|
||||||
|
);
|
||||||
|
for (const t of targets) await processModule(cli, t);
|
||||||
|
console.log("\nGotowe.");
|
||||||
@@ -57,6 +57,7 @@ DRY_RUN: bool = False # globalny tryb symulacji (nic nie zmienia)
|
|||||||
ZIP_MODE: str = "ALL" # "ALL" | "MODULE_ONLY" | "OFF"
|
ZIP_MODE: str = "ALL" # "ALL" | "MODULE_ONLY" | "OFF"
|
||||||
# Katalog wyjściowy na archiwa .zip; jeśli None, użyje SOURCE_BASE_DIR/_zips
|
# Katalog wyjściowy na archiwa .zip; jeśli None, użyje SOURCE_BASE_DIR/_zips
|
||||||
ZIP_OUTPUT_DIR: Optional[str] = r"C:\Release packs"
|
ZIP_OUTPUT_DIR: Optional[str] = r"C:\Release packs"
|
||||||
|
CHECK_MODE: bool = False # tylko walidacja – brak zmian, wyjście !=0 jeśli są akcje
|
||||||
|
|
||||||
# ==========================
|
# ==========================
|
||||||
# KONIEC KONFIGURACJI
|
# KONIEC KONFIGURACJI
|
||||||
@@ -316,23 +317,24 @@ def run_git_commands(repo_dir: Path, commit_msg: str, dry_run: bool) -> None:
|
|||||||
def run(cmd: List[str]) -> None:
|
def run(cmd: List[str]) -> None:
|
||||||
rc, out = sh(cmd, repo_dir)
|
rc, out = sh(cmd, repo_dir)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise RuntimeError(f"Błąd podczas uruchamiania: {' '.join(cmd)}\nWyjście:\n{out}")
|
raise RuntimeError(f"Blad podczas uruchamiania: {' '.join(cmd)}\nWyjscie:\n{out}")
|
||||||
if out.strip():
|
if out.strip():
|
||||||
print(out)
|
print(out)
|
||||||
|
|
||||||
run(["git", "add", "-A"])
|
run(["git", "add", "-A"])
|
||||||
rc, out = sh(["git", "commit", "-m", commit_msg], repo_dir)
|
rc, out = sh(["git", "commit", "-m", commit_msg], repo_dir)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise RuntimeError(f"Błąd podczas git commit:\n{out}")
|
raise RuntimeError(f"Blad podczas git commit:\n{out}")
|
||||||
if out.strip():
|
if out.strip():
|
||||||
print(out)
|
print(out)
|
||||||
run(["git", "push"])
|
run(["git", "push"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def preflight_module(src_dir: Path, dry_run: bool) -> Tuple[str, str]:
|
def preflight_module(src_dir: Path, dry_run: bool) -> Tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Moduł: odczyt module.json, bump, zapis (jeśli nie dry_run), commit i push.
|
Moduł: odczyt module.json, bump, zapis (jeśli nie dry_run), commit i push.
|
||||||
Zwraca (module_id, new_version).
|
Zwraca (module_id, new_version, commit_sha lub None przy dry-run).
|
||||||
"""
|
"""
|
||||||
manifest_path = src_dir / "module.json"
|
manifest_path = src_dir / "module.json"
|
||||||
manifest = read_manifest(manifest_path)
|
manifest = read_manifest(manifest_path)
|
||||||
@@ -363,7 +365,7 @@ def preflight_non_module(src_dir: Path, dry_run: bool) -> str:
|
|||||||
- jeśli brak -> '0.1'
|
- jeśli brak -> '0.1'
|
||||||
- jeśli jest -> inkrementuj x
|
- jeśli jest -> inkrementuj x
|
||||||
Zapis (nie dry-run) oraz osobny commit/push.
|
Zapis (nie dry-run) oraz osobny commit/push.
|
||||||
Zwraca new_version (np. '0.4').
|
Zwraca (new_version, commit_sha lub None przy dry-run).
|
||||||
"""
|
"""
|
||||||
nfo = src_dir / "version.nfo"
|
nfo = src_dir / "version.nfo"
|
||||||
if not nfo.exists():
|
if not nfo.exists():
|
||||||
@@ -378,14 +380,16 @@ def preflight_non_module(src_dir: Path, dry_run: bool) -> str:
|
|||||||
|
|
||||||
# istnieje -> wczytaj i bumpnij
|
# istnieje -> wczytaj i bumpnij
|
||||||
content = nfo.read_text(encoding="utf-8").strip()
|
content = nfo.read_text(encoding="utf-8").strip()
|
||||||
if not content.startswith("0."):
|
if not (content.startswith("0.") or content.startswith("1.")):
|
||||||
raise ValueError(f"{norm(nfo)}: oczekiwano formatu '0.x', otrzymano '{content}'")
|
raise ValueError(f"{norm(nfo)}: oczekiwano formatu '0/1.x', otrzymano '{content}'")
|
||||||
tail = content.split(".", 1)[1]
|
tail = content.split(".", 1)[1]
|
||||||
|
head = content.split(".",1)[0]
|
||||||
try:
|
try:
|
||||||
|
_ = int(head)
|
||||||
x = int(tail)
|
x = int(tail)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(f"{norm(nfo)}: część po '0.' nie jest liczbą: '{tail}'")
|
raise ValueError(f"{norm(nfo)}: część po '0/1.' nie jest liczbą: '{tail}'")
|
||||||
new_version = f"0.{x+1}"
|
new_version = f"{head}.{x+1}"
|
||||||
print(f"[PRE][FOLDER] {src_dir.name}: version {content} -> {new_version}")
|
print(f"[PRE][FOLDER] {src_dir.name}: version {content} -> {new_version}")
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
@@ -520,6 +524,12 @@ def parse_args() -> argparse.Namespace:
|
|||||||
default=ZIP_OUTPUT_DIR,
|
default=ZIP_OUTPUT_DIR,
|
||||||
help="Katalog wyjściowy na zipy (domyślnie: SOURCE_BASE_DIR/_zips)")
|
help="Katalog wyjściowy na zipy (domyślnie: SOURCE_BASE_DIR/_zips)")
|
||||||
|
|
||||||
|
# Tryb check (tylko walidacja)
|
||||||
|
p.add_argument("--check", dest="check", action="store_true",
|
||||||
|
help="Tylko sprawdzenie zmian (wymusza dry-run, kod wyjścia 1 przy wymaganych akcjach).")
|
||||||
|
p.set_defaults(check=CHECK_MODE)
|
||||||
|
|
||||||
|
|
||||||
return p.parse_args()
|
return p.parse_args()
|
||||||
|
|
||||||
|
|
||||||
@@ -548,10 +558,11 @@ def process_module(src_dir: Path, modules_base: Path, confirm_delete: bool, dry_
|
|||||||
print("[INFO] (dry-run) Pytanie o potwierdzenie pominięte.")
|
print("[INFO] (dry-run) Pytanie o potwierdzenie pominięte.")
|
||||||
|
|
||||||
# ZIP po commicie, przed podmianą
|
# ZIP po commicie, przed podmianą
|
||||||
|
zip_path: Optional[Path] = None
|
||||||
if should_zip(is_module=True, zip_mode=ARGS.zip_mode):
|
if should_zip(is_module=True, zip_mode=ARGS.zip_mode):
|
||||||
out_dir = ensure_zip_output_dir(Path(ARGS.source_base_dir), ARGS.zip_output_dir)
|
out_dir = ensure_zip_output_dir(Path(ARGS.source_base_dir), ARGS.zip_output_dir)
|
||||||
try:
|
try:
|
||||||
make_zip_for_folder(src_dir, new_ver, out_dir, dry_run)
|
zip_path = make_zip_for_folder(src_dir, new_ver, out_dir, dry_run)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Błąd zipowania modułu {src_dir.name}: {e}") from e
|
raise RuntimeError(f"Błąd zipowania modułu {src_dir.name}: {e}") from e
|
||||||
|
|
||||||
@@ -566,24 +577,27 @@ def process_module(src_dir: Path, modules_base: Path, confirm_delete: bool, dry_
|
|||||||
print(f"[DONE][MODULE] {src_dir.name} -> commit 'sync: {module_id} v{new_ver}' wykonany." if not dry_run
|
print(f"[DONE][MODULE] {src_dir.name} -> commit 'sync: {module_id} v{new_ver}' wykonany." if not dry_run
|
||||||
else f"[DRY-RUN][MODULE] {src_dir.name} -> commit planowany.")
|
else f"[DRY-RUN][MODULE] {src_dir.name} -> commit planowany.")
|
||||||
|
|
||||||
|
|
||||||
def process_non_module(src_dir: Path, dry_run: bool) -> None:
|
def process_non_module(src_dir: Path, dry_run: bool) -> None:
|
||||||
new_ver = preflight_non_module(src_dir, dry_run=dry_run)
|
new_ver = preflight_non_module(src_dir, dry_run=dry_run)
|
||||||
# ZIP po commicie (jeśli ustawienie pozwala)
|
# ZIP po commicie (jeśli ustawienie pozwala)
|
||||||
|
zip_path: Optional[Path] = None
|
||||||
if should_zip(is_module=False, zip_mode=ARGS.zip_mode):
|
if should_zip(is_module=False, zip_mode=ARGS.zip_mode):
|
||||||
out_dir = ensure_zip_output_dir(Path(ARGS.source_base_dir), ARGS.zip_output_dir)
|
out_dir = ensure_zip_output_dir(Path(ARGS.source_base_dir), ARGS.zip_output_dir)
|
||||||
try:
|
try:
|
||||||
make_zip_for_folder(src_dir, new_ver, out_dir, dry_run)
|
zip_path = make_zip_for_folder(src_dir, new_ver, out_dir, dry_run)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise RuntimeError(f"Błąd zipowania folderu {src_dir.name}: {e}") from e
|
raise RuntimeError(f"Błąd zipowania folderu {src_dir.name}: {e}") from e
|
||||||
print(f"[DONE][FOLDER] {src_dir.name} -> version.nfo = {new_ver}; commit wykonany." if not dry_run
|
print(f"[DONE][FOLDER] {src_dir.name} -> version.nfo = {new_ver}; commit wykonany." if not dry_run
|
||||||
else f"[DRY-RUN][FOLDER] {src_dir.name} -> version.nfo będzie {new_ver}; commit planowany.")
|
else f"[DRY-RUN][FOLDER] {src_dir.name} -> version.nfo będzie {new_ver}; commit planowany.")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
global ARGS
|
global ARGS
|
||||||
ARGS = parse_args()
|
ARGS = parse_args()
|
||||||
|
|
||||||
|
check_mode = ARGS.check
|
||||||
|
if check_mode:
|
||||||
|
ARGS.dry_run = True
|
||||||
|
|
||||||
mode = ARGS.mode
|
mode = ARGS.mode
|
||||||
user = ARGS.user
|
user = ARGS.user
|
||||||
appdata_kind = ARGS.appdata_kind if ARGS.appdata_kind != "None" else None
|
appdata_kind = ARGS.appdata_kind if ARGS.appdata_kind != "None" else None
|
||||||
@@ -595,39 +609,57 @@ def main() -> int:
|
|||||||
dry_run = ARGS.dry_run
|
dry_run = ARGS.dry_run
|
||||||
|
|
||||||
if not source_base.is_dir():
|
if not source_base.is_dir():
|
||||||
raise FileNotFoundError(f"Katalog źródłowy nie istnieje: {norm(source_base)}")
|
raise FileNotFoundError(f"Katalog zrodlowy nie istnieje: {norm(source_base)}")
|
||||||
|
|
||||||
ensure_git_repo(source_base)
|
ensure_git_repo(source_base)
|
||||||
|
|
||||||
# Ustal bazę modules (tylko raz)
|
|
||||||
modules_base = resolve_modules_base(user, relative_pattern, appdata_kind, modules_override)
|
modules_base = resolve_modules_base(user, relative_pattern, appdata_kind, modules_override)
|
||||||
print(f"[INFO] Katalog modules base: {norm(modules_base)}")
|
print(f"[INFO] Katalog modules base: {norm(modules_base)}")
|
||||||
|
|
||||||
|
pending_actions = False
|
||||||
|
|
||||||
if mode == "single":
|
if mode == "single":
|
||||||
src_dir = source_base / module_subdir_single
|
src_dir = source_base / module_subdir_single
|
||||||
if not src_dir.is_dir():
|
if not src_dir.is_dir():
|
||||||
raise FileNotFoundError(f"[SINGLE] Brak katalogu: {norm(src_dir)}")
|
raise FileNotFoundError(f"[SINGLE] Brak katalogu: {norm(src_dir)}")
|
||||||
# Root bump (globalna wersja) – tylko jeśli "są zmiany do przetworzenia" (single -> True)
|
|
||||||
maybe_bump_root_version(source_base, changed_any=True, dry_run=dry_run)
|
root_bumped = maybe_bump_root_version(source_base, changed_any=True, dry_run=dry_run)
|
||||||
|
if root_bumped:
|
||||||
|
pending_actions = True
|
||||||
|
|
||||||
|
pending_actions = True
|
||||||
|
|
||||||
if (src_dir / "module.json").is_file():
|
if (src_dir / "module.json").is_file():
|
||||||
print(f"[MODE=SINGLE][MODULE] {src_dir.name}")
|
print(f"[MODE=SINGLE][MODULE] {src_dir.name}")
|
||||||
process_module(src_dir, modules_base, confirm_delete, dry_run)
|
process_module(src_dir, modules_base, confirm_delete, dry_run)
|
||||||
else:
|
else:
|
||||||
print(f"[MODE=SINGLE][FOLDER] {src_dir.name}")
|
print(f"[MODE=SINGLE][FOLDER] {src_dir.name}")
|
||||||
process_non_module(src_dir, dry_run)
|
process_non_module(src_dir, dry_run)
|
||||||
print("[SUKCES] Tryb SINGLE zakończony.")
|
print("[SUKCES] Tryb SINGLE zakonczony.")
|
||||||
|
if check_mode:
|
||||||
|
print("[CHECK] Wykryto oczekujace akcje (tryb single).")
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# AUTO: wykryj zmienione katalogi
|
|
||||||
changed = git_changed_top_level_dirs(source_base)
|
changed = git_changed_top_level_dirs(source_base)
|
||||||
print(f"[AUTO] Zmienione katalogi w {norm(source_base)}: {changed if changed else '(brak)'}")
|
print(f"[AUTO] Zmienione katalogi w {norm(source_base)}: {changed if changed else '(brak)'}")
|
||||||
# Root bump (globalna wersja) – tylko jeśli są jakiekolwiek zmiany do przetworzenia
|
if changed:
|
||||||
maybe_bump_root_version(source_base, changed_any=bool(changed), dry_run=dry_run)
|
pending_actions = True
|
||||||
|
|
||||||
|
root_bumped = maybe_bump_root_version(source_base, changed_any=bool(changed), dry_run=dry_run)
|
||||||
|
if root_bumped:
|
||||||
|
pending_actions = True
|
||||||
|
|
||||||
if not changed:
|
if not changed:
|
||||||
print("[INFO] Brak zmian do przetworzenia.")
|
print("[INFO] Brak zmian do przetworzenia.")
|
||||||
|
if check_mode:
|
||||||
|
if pending_actions:
|
||||||
|
print("[CHECK] Wykryto oczekujace akcje.")
|
||||||
|
return 1
|
||||||
|
print("[CHECK] OK - brak akcji wymaganych.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Przetwarzaj w stałej kolejności
|
# Przetwarzaj w stalej kolejnosci
|
||||||
for name in changed:
|
for name in changed:
|
||||||
src_dir = source_base / name
|
src_dir = source_base / name
|
||||||
if (src_dir / "module.json").is_file():
|
if (src_dir / "module.json").is_file():
|
||||||
@@ -637,7 +669,10 @@ def main() -> int:
|
|||||||
print(f"\n[AUTO][FOLDER] {name}")
|
print(f"\n[AUTO][FOLDER] {name}")
|
||||||
process_non_module(src_dir, dry_run)
|
process_non_module(src_dir, dry_run)
|
||||||
|
|
||||||
print("\n[SUKCES] Tryb AUTO zakończony.")
|
print("\n[SUKCES] Tryb AUTO zakonczony.")
|
||||||
|
if check_mode and pending_actions:
|
||||||
|
print("[CHECK] Wykryto oczekujace akcje.")
|
||||||
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3
|
0.5
|
||||||
|
|||||||
@@ -1,516 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Control Board Monitor - v0.1
|
|
||||||
|
|
||||||
A lightweight Windows-friendly Python GUI to monitor server variables exposed via
|
|
||||||
HTTP GET as "/?variable=VARNAME".
|
|
||||||
|
|
||||||
- Defaults: host="localhost", port=8945 (edit globals below or use the GUI).
|
|
||||||
- Refreshes automatically every minute.
|
|
||||||
- Fetches variables sequentially with a configurable Requests-Per-Second (RPS) cap
|
|
||||||
and automatic backoff on HTTP 429/5xx to avoid overwhelming the server.
|
|
||||||
- Attempts to discover the variable list via WEBSERVER_LIST_VARIABLES_JSON, then
|
|
||||||
WEBSERVER_LIST_VARIABLES. You can also paste/import a list manually in the GUI.
|
|
||||||
- First version focuses on read-only monitoring. Next versions can add POST controls
|
|
||||||
to set variables and turn some values into indicator widgets.
|
|
||||||
|
|
||||||
No external GUI frameworks are required (uses Tkinter from the standard library).
|
|
||||||
No non-stdlib HTTP client is required (uses urllib).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import queue
|
|
||||||
import re
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
try:
|
|
||||||
import tkinter as tk
|
|
||||||
from tkinter import ttk, messagebox, simpledialog
|
|
||||||
except Exception as e:
|
|
||||||
raise SystemExit("Tkinter is required to run this app.")
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
|
||||||
# Global configuration
|
|
||||||
# =====================
|
|
||||||
SERVER_HOST: str = "localhost" # default host
|
|
||||||
SERVER_PORT: int = 8785 # default port (as requested)
|
|
||||||
REFRESH_INTERVAL_S: int = 60 # run a full sweep roughly every minute
|
|
||||||
MAX_RPS: float = 5.0 # cap average requests/sec to avoid overload
|
|
||||||
REQUEST_TIMEOUT_S: float = 5.0 # per-request timeout
|
|
||||||
USER_AGENT: str = "ControlBoardMonitor/0.1 (+tkinter)"
|
|
||||||
CONNECT_TIMEOUT_S: float = 5.0
|
|
||||||
|
|
||||||
# Backoff
|
|
||||||
BACKOFF_BASE_S: float = 1.0 # initial backoff
|
|
||||||
BACKOFF_MAX_S: float = 15.0 # max backoff between retries
|
|
||||||
MAX_RETRIES: int = 2 # retries per variable within a cycle
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
|
||||||
# Helper utilities
|
|
||||||
# =====================
|
|
||||||
def http_get(base_url: str, params: Dict[str, str]) -> Tuple[int, str, Dict[str, str]]:
|
|
||||||
"""Perform a GET with urllib. Returns (status, text, headers)."""
|
|
||||||
url = base_url + "?" + urllib.parse.urlencode(params)
|
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT_S) as resp:
|
|
||||||
status = resp.getcode()
|
|
||||||
body_bytes = resp.read()
|
|
||||||
try:
|
|
||||||
body = body_bytes.decode("utf-8", errors="replace")
|
|
||||||
except Exception:
|
|
||||||
body = body_bytes.decode("latin-1", errors="replace")
|
|
||||||
headers = {k.lower(): v for k, v in resp.getheaders()}
|
|
||||||
return status, body, headers
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
try:
|
|
||||||
body = e.read().decode("utf-8", errors="replace")
|
|
||||||
except Exception:
|
|
||||||
body = str(e)
|
|
||||||
return e.code, body, dict(e.headers or {})
|
|
||||||
except Exception as e:
|
|
||||||
return 0, str(e), {}
|
|
||||||
|
|
||||||
|
|
||||||
def build_base_url(host: str, port: int) -> str:
|
|
||||||
scheme = "http"
|
|
||||||
return f"{scheme}://{host}:{port}/"
|
|
||||||
|
|
||||||
|
|
||||||
def parse_variable_names_from_html_index(html_text: str) -> List[str]:
|
|
||||||
"""Best-effort extraction of variable names from the simple HTML index page style."""
|
|
||||||
vars_found: List[str] = []
|
|
||||||
for m in re.finditer(r"[?&]variable=([A-Z0-9_]+)", html_text):
|
|
||||||
vars_found.append(m.group(1))
|
|
||||||
# De-duplicate preserving order
|
|
||||||
seen = set()
|
|
||||||
uniq = []
|
|
||||||
for v in vars_found:
|
|
||||||
if v not in seen:
|
|
||||||
uniq.append(v)
|
|
||||||
seen.add(v)
|
|
||||||
return uniq
|
|
||||||
|
|
||||||
|
|
||||||
def coerce_preview(value: str, maxlen: int = 80) -> str:
|
|
||||||
v = value.strip().replace("\r", " ").replace("\n", " ")
|
|
||||||
if len(v) > maxlen:
|
|
||||||
return v[:maxlen - 1] + "…"
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
def infer_status_color(value: str) -> str:
|
|
||||||
"""Very simple status → tag mapping to mimic control-board feel."""
|
|
||||||
val = value.strip().lower()
|
|
||||||
if val in {"1", "true", "on", "open", "active", "ok", "running", "enabled"}:
|
|
||||||
return "ok"
|
|
||||||
if val in {"0", "false", "off", "closed", "inactive", "error", "fault", "disabled"}:
|
|
||||||
return "bad"
|
|
||||||
# Warn if it's empty or has obvious error keywords
|
|
||||||
if val == "" or "exception" in val or "error" in val:
|
|
||||||
return "bad"
|
|
||||||
return "neutral"
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
|
||||||
# Data model
|
|
||||||
# =====================
|
|
||||||
@dataclass
|
|
||||||
class VarInfo:
|
|
||||||
name: str
|
|
||||||
last_value: str = ""
|
|
||||||
last_updated: float = 0.0
|
|
||||||
last_status: int = 0
|
|
||||||
error: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
|
||||||
# Poller thread
|
|
||||||
# =====================
|
|
||||||
class Poller(threading.Thread):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
host: str,
|
|
||||||
port: int,
|
|
||||||
variables: List[str],
|
|
||||||
ui_queue: queue.Queue,
|
|
||||||
refresh_interval: int,
|
|
||||||
max_rps: float,
|
|
||||||
stop_event: threading.Event,
|
|
||||||
paused_event: threading.Event,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(daemon=True)
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.variables = variables
|
|
||||||
self.ui_queue = ui_queue
|
|
||||||
self.refresh_interval = int(max(5, refresh_interval))
|
|
||||||
self.max_rps = max(0.5, max_rps)
|
|
||||||
self.stop_event = stop_event
|
|
||||||
self.paused_event = paused_event
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
base_url = build_base_url(self.host, self.port)
|
|
||||||
min_spacing = 1.0 / self.max_rps # seconds between requests
|
|
||||||
while not self.stop_event.is_set():
|
|
||||||
cycle_start = time.time()
|
|
||||||
self.ui_queue.put(("cycle_start", datetime.now().strftime("%H:%M:%S")))
|
|
||||||
for name in list(self.variables):
|
|
||||||
# Allow pausing mid-cycle
|
|
||||||
while self.paused_event.is_set() and not self.stop_event.is_set():
|
|
||||||
time.sleep(0.1)
|
|
||||||
if self.stop_event.is_set():
|
|
||||||
break
|
|
||||||
|
|
||||||
attempt = 0
|
|
||||||
backoff = BACKOFF_BASE_S
|
|
||||||
while attempt <= MAX_RETRIES and not self.stop_event.is_set():
|
|
||||||
attempt += 1
|
|
||||||
t0 = time.time()
|
|
||||||
status, body, headers = http_get(base_url, {"variable": name})
|
|
||||||
|
|
||||||
# Success path
|
|
||||||
if status == 200:
|
|
||||||
self.ui_queue.put(("update", name, body, status))
|
|
||||||
break
|
|
||||||
|
|
||||||
# Backoff on overload or server error
|
|
||||||
if status in (429, 500, 502, 503, 504, 0):
|
|
||||||
self.ui_queue.put(("error", name, status, coerce_preview(body, 200)))
|
|
||||||
time.sleep(backoff)
|
|
||||||
backoff = min(BACKOFF_MAX_S, backoff * 2.0)
|
|
||||||
else:
|
|
||||||
# Other HTTP errors: report once and stop retrying
|
|
||||||
self.ui_queue.put(("error", name, status, coerce_preview(body, 200)))
|
|
||||||
break
|
|
||||||
|
|
||||||
# Respect RPS even with errors
|
|
||||||
dt = time.time() - t0
|
|
||||||
if dt < min_spacing:
|
|
||||||
time.sleep(min_spacing - dt)
|
|
||||||
|
|
||||||
# Spacing between requests
|
|
||||||
dt = time.time() - t0
|
|
||||||
if dt < min_spacing:
|
|
||||||
time.sleep(min_spacing - dt)
|
|
||||||
|
|
||||||
# Sleep to align roughly to the refresh interval
|
|
||||||
cycle_dt = time.time() - cycle_start
|
|
||||||
remaining = self.refresh_interval - cycle_dt
|
|
||||||
if remaining > 0:
|
|
||||||
for _ in range(int(remaining * 10)):
|
|
||||||
if self.stop_event.is_set():
|
|
||||||
break
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
self.ui_queue.put(("stopped", ""))
|
|
||||||
|
|
||||||
|
|
||||||
# =====================
|
|
||||||
# GUI
|
|
||||||
# =====================
|
|
||||||
class App(tk.Tk):
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.title("Control Board Monitor")
|
|
||||||
self.geometry("1100x700")
|
|
||||||
try:
|
|
||||||
self.iconbitmap(default="") # no icon file bundled
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# State
|
|
||||||
self.vars: Dict[str, VarInfo] = {}
|
|
||||||
self.stop_event = threading.Event()
|
|
||||||
self.paused_event = threading.Event()
|
|
||||||
self.ui_queue: queue.Queue = queue.Queue()
|
|
||||||
self.poller: Optional[Poller] = None
|
|
||||||
self.variables_list: List[str] = []
|
|
||||||
|
|
||||||
# Top controls
|
|
||||||
controls = ttk.Frame(self, padding=10)
|
|
||||||
controls.pack(side=tk.TOP, fill=tk.X)
|
|
||||||
|
|
||||||
self.host_var = tk.StringVar(value=SERVER_HOST)
|
|
||||||
self.port_var = tk.IntVar(value=SERVER_PORT)
|
|
||||||
self.refresh_var = tk.IntVar(value=REFRESH_INTERVAL_S)
|
|
||||||
self.rps_var = tk.DoubleVar(value=MAX_RPS)
|
|
||||||
|
|
||||||
ttk.Label(controls, text="Host:").grid(row=0, column=0, sticky="w")
|
|
||||||
ttk.Entry(controls, textvariable=self.host_var, width=16).grid(row=0, column=1, padx=(0, 12))
|
|
||||||
ttk.Label(controls, text="Port:").grid(row=0, column=2, sticky="w")
|
|
||||||
ttk.Entry(controls, textvariable=self.port_var, width=8).grid(row=0, column=3, padx=(0, 12))
|
|
||||||
ttk.Label(controls, text="Refresh (s):").grid(row=0, column=4, sticky="w")
|
|
||||||
ttk.Entry(controls, textvariable=self.refresh_var, width=8).grid(row=0, column=5, padx=(0, 12))
|
|
||||||
ttk.Label(controls, text="Max RPS:").grid(row=0, column=6, sticky="w")
|
|
||||||
ttk.Entry(controls, textvariable=self.rps_var, width=8).grid(row=0, column=7, padx=(0, 12))
|
|
||||||
|
|
||||||
self.status_lbl = ttk.Label(controls, text="Idle", foreground="#666")
|
|
||||||
self.status_lbl.grid(row=0, column=8, padx=(10, 0))
|
|
||||||
|
|
||||||
btns = ttk.Frame(controls)
|
|
||||||
btns.grid(row=0, column=9, padx=10, sticky="e")
|
|
||||||
self.start_btn = ttk.Button(btns, text="Start", command=self.start_polling)
|
|
||||||
self.start_btn.grid(row=0, column=0, padx=4)
|
|
||||||
self.pause_btn = ttk.Button(btns, text="Pause", command=self.toggle_pause, state="disabled")
|
|
||||||
self.pause_btn.grid(row=0, column=1, padx=4)
|
|
||||||
self.reload_btn = ttk.Button(btns, text="Reload Vars", command=self.reload_variables)
|
|
||||||
self.reload_btn.grid(row=0, column=2, padx=4)
|
|
||||||
self.import_btn = ttk.Button(btns, text="Import List", command=self.import_list_dialog)
|
|
||||||
self.import_btn.grid(row=0, column=3, padx=4)
|
|
||||||
|
|
||||||
# Filter/Search
|
|
||||||
search_frame = ttk.Frame(self, padding=(10, 0, 10, 10))
|
|
||||||
search_frame.pack(side=tk.TOP, fill=tk.X)
|
|
||||||
ttk.Label(search_frame, text="Filter:").pack(side=tk.LEFT)
|
|
||||||
self.filter_var = tk.StringVar(value="")
|
|
||||||
self.filter_var.trace_add("write", lambda *_: self.refresh_tree())
|
|
||||||
ttk.Entry(search_frame, textvariable=self.filter_var, width=50).pack(side=tk.LEFT, padx=8)
|
|
||||||
|
|
||||||
# Tree (control-board style with groups)
|
|
||||||
columns = ("value", "updated", "status")
|
|
||||||
self.tree = ttk.Treeview(self, columns=columns, show="tree headings", height=26)
|
|
||||||
self.tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
||||||
|
|
||||||
self.tree.heading("#0", text="Variable")
|
|
||||||
self.tree.heading("value", text="Value")
|
|
||||||
self.tree.heading("updated", text="Updated")
|
|
||||||
self.tree.heading("status", text="HTTP")
|
|
||||||
|
|
||||||
self.tree.column("#0", width=420, anchor="w")
|
|
||||||
self.tree.column("value", width=420, anchor="w")
|
|
||||||
self.tree.column("updated", width=120, anchor="center")
|
|
||||||
self.tree.column("status", width=60, anchor="center")
|
|
||||||
|
|
||||||
# Style tags for a control-board feel
|
|
||||||
style = ttk.Style(self)
|
|
||||||
try:
|
|
||||||
style.theme_use("clam")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self.tree.tag_configure("ok", background="#eaffea") # soft green
|
|
||||||
self.tree.tag_configure("bad", background="#ffecec") # soft red
|
|
||||||
self.tree.tag_configure("neutral", background="#f5f7fb") # soft gray-blue
|
|
||||||
|
|
||||||
# Footer
|
|
||||||
footer = ttk.Frame(self, padding=10)
|
|
||||||
footer.pack(side=tk.BOTTOM, fill=tk.X)
|
|
||||||
self.cycle_lbl = ttk.Label(footer, text="Last cycle: —")
|
|
||||||
self.cycle_lbl.pack(side=tk.LEFT)
|
|
||||||
|
|
||||||
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
|
||||||
|
|
||||||
# Initial discovery
|
|
||||||
self.after(200, self.reload_variables)
|
|
||||||
|
|
||||||
# ------------- Variable discovery -------------
|
|
||||||
def reload_variables(self) -> None:
|
|
||||||
host = self.host_var.get().strip()
|
|
||||||
port = int(self.port_var.get())
|
|
||||||
base = build_base_url(host, port)
|
|
||||||
|
|
||||||
discovered: List[str] = []
|
|
||||||
# Try JSON list first
|
|
||||||
status, body, _ = http_get(base, {"variable": "WEBSERVER_LIST_VARIABLES_JSON"})
|
|
||||||
if status == 200:
|
|
||||||
try:
|
|
||||||
data = json.loads(body)
|
|
||||||
if isinstance(data, list) and all(isinstance(x, str) for x in data):
|
|
||||||
discovered = list(dict.fromkeys(data)) # preserve order, unique
|
|
||||||
except Exception:
|
|
||||||
discovered = []
|
|
||||||
|
|
||||||
# Fallback: parse the human/HTML list
|
|
||||||
if not discovered:
|
|
||||||
status, body, _ = http_get(base, {"variable": "WEBSERVER_LIST_VARIABLES"})
|
|
||||||
if status == 200 and body:
|
|
||||||
cand = parse_variable_names_from_html_index(body)
|
|
||||||
if cand:
|
|
||||||
discovered = cand
|
|
||||||
|
|
||||||
# If still empty, offer a minimal starter set
|
|
||||||
if not discovered:
|
|
||||||
discovered = [
|
|
||||||
"TIME", "TIME_STAMP", "CORE_TEMP", "CORE_PRESSURE", "CORE_STATE",
|
|
||||||
"COOLANT_CORE_PRESSURE", "COOLANT_CORE_FLOW_IN", "COOLANT_CORE_FLOW_OUT",
|
|
||||||
"STEAM_TURBINE_0_RPM", "POWER_FROM_TURBINE_KW", "POWER_DEMAND_MW",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Update local state
|
|
||||||
self.variables_list = discovered
|
|
||||||
self.vars.clear()
|
|
||||||
for v in discovered:
|
|
||||||
self.vars[v] = VarInfo(name=v)
|
|
||||||
|
|
||||||
self.status_lbl.configure(text=f"Loaded {len(discovered)} vars")
|
|
||||||
self.refresh_tree()
|
|
||||||
|
|
||||||
def import_list_dialog(self) -> None:
|
|
||||||
txt = simpledialog.askstring(
|
|
||||||
"Import variable names",
|
|
||||||
"Paste variable names separated by spaces, commas, or newlines:",
|
|
||||||
parent=self,
|
|
||||||
)
|
|
||||||
if not txt:
|
|
||||||
return
|
|
||||||
names = re.findall(r"[A-Z0-9_]+", txt.upper())
|
|
||||||
if not names:
|
|
||||||
messagebox.showwarning("Nothing found", "No variable-like tokens found.")
|
|
||||||
return
|
|
||||||
# Deduplicate while preserving order
|
|
||||||
uniq = []
|
|
||||||
seen = set()
|
|
||||||
for n in names:
|
|
||||||
if n not in seen:
|
|
||||||
uniq.append(n); seen.add(n)
|
|
||||||
self.variables_list = uniq
|
|
||||||
self.vars.clear()
|
|
||||||
for v in uniq:
|
|
||||||
self.vars[v] = VarInfo(name=v)
|
|
||||||
self.status_lbl.configure(text=f"Imported {len(uniq)} vars")
|
|
||||||
self.refresh_tree()
|
|
||||||
|
|
||||||
# ------------- Poller control -------------
|
|
||||||
def start_polling(self) -> None:
|
|
||||||
if self.poller and self.poller.is_alive():
|
|
||||||
messagebox.showinfo("Already running", "Polling is already active.")
|
|
||||||
return
|
|
||||||
if not self.variables_list:
|
|
||||||
messagebox.showwarning("No variables", "Variable list is empty. Reload or import a list first.")
|
|
||||||
return
|
|
||||||
self.stop_event.clear()
|
|
||||||
self.paused_event.clear()
|
|
||||||
self.poller = Poller(
|
|
||||||
host=self.host_var.get().strip(),
|
|
||||||
port=int(self.port_var.get()),
|
|
||||||
variables=self.variables_list,
|
|
||||||
ui_queue=self.ui_queue,
|
|
||||||
refresh_interval=int(self.refresh_var.get()),
|
|
||||||
max_rps=float(self.rps_var.get()),
|
|
||||||
stop_event=self.stop_event,
|
|
||||||
paused_event=self.paused_event,
|
|
||||||
)
|
|
||||||
self.poller.start()
|
|
||||||
self.start_btn.configure(state="disabled")
|
|
||||||
self.pause_btn.configure(state="normal", text="Pause")
|
|
||||||
self.status_lbl.configure(text="Running…")
|
|
||||||
self.after(100, self.drain_queue)
|
|
||||||
|
|
||||||
def toggle_pause(self) -> None:
|
|
||||||
if not (self.poller and self.poller.is_alive()):
|
|
||||||
return
|
|
||||||
if self.paused_event.is_set():
|
|
||||||
self.paused_event.clear()
|
|
||||||
self.pause_btn.configure(text="Pause")
|
|
||||||
self.status_lbl.configure(text="Running…")
|
|
||||||
else:
|
|
||||||
self.paused_event.set()
|
|
||||||
self.pause_btn.configure(text="Resume")
|
|
||||||
self.status_lbl.configure(text="Paused")
|
|
||||||
|
|
||||||
def drain_queue(self) -> None:
|
|
||||||
"""Receive updates from the poller and apply to the UI safely from the main thread."""
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
item = self.ui_queue.get_nowait()
|
|
||||||
kind = item[0]
|
|
||||||
if kind == "update":
|
|
||||||
_, name, value, status = item
|
|
||||||
vi = self.vars.get(name) or VarInfo(name=name)
|
|
||||||
vi.last_value = value
|
|
||||||
vi.last_updated = time.time()
|
|
||||||
vi.last_status = status
|
|
||||||
vi.error = None
|
|
||||||
self.vars[name] = vi
|
|
||||||
elif kind == "error":
|
|
||||||
_, name, status, msg = item
|
|
||||||
vi = self.vars.get(name) or VarInfo(name=name)
|
|
||||||
vi.last_status = status
|
|
||||||
vi.error = msg
|
|
||||||
vi.last_updated = time.time()
|
|
||||||
self.vars[name] = vi
|
|
||||||
elif kind == "cycle_start":
|
|
||||||
_, hhmmss = item
|
|
||||||
self.cycle_lbl.configure(text=f"Last cycle start: {hhmmss}")
|
|
||||||
elif kind == "stopped":
|
|
||||||
self.status_lbl.configure(text="Stopped")
|
|
||||||
self.ui_queue.task_done()
|
|
||||||
except queue.Empty:
|
|
||||||
pass
|
|
||||||
self.refresh_tree()
|
|
||||||
if self.poller and self.poller.is_alive():
|
|
||||||
self.after(250, self.drain_queue) # continue pumping
|
|
||||||
|
|
||||||
def stop_polling(self) -> None:
|
|
||||||
if self.poller and self.poller.is_alive():
|
|
||||||
self.stop_event.set()
|
|
||||||
# give it a moment to end gracefully
|
|
||||||
for _ in range(20):
|
|
||||||
if not self.poller.is_alive():
|
|
||||||
break
|
|
||||||
time.sleep(0.05)
|
|
||||||
self.start_btn.configure(state="normal")
|
|
||||||
self.pause_btn.configure(state="disabled", text="Pause")
|
|
||||||
|
|
||||||
# ------------- Tree rendering -------------
|
|
||||||
def refresh_tree(self) -> None:
|
|
||||||
# Clear existing
|
|
||||||
self.tree.delete(*self.tree.get_children())
|
|
||||||
|
|
||||||
# Group by prefix (e.g. CORE_, COOLANT_, STEAM_)
|
|
||||||
groups: Dict[str, List[VarInfo]] = {}
|
|
||||||
filt = self.filter_var.get().strip().upper()
|
|
||||||
|
|
||||||
for name, vi in self.vars.items():
|
|
||||||
if filt and filt not in name.upper():
|
|
||||||
# Also allow filtering by value preview
|
|
||||||
if vi.last_value and filt not in vi.last_value.upper():
|
|
||||||
continue
|
|
||||||
prefix = name.split("_", 1)[0] if "_" in name else "MISC"
|
|
||||||
groups.setdefault(prefix, []).append(vi)
|
|
||||||
|
|
||||||
# Sort groups alphabetically, vars within group by name
|
|
||||||
for g in sorted(groups.keys()):
|
|
||||||
parent = self.tree.insert("", "end", text=g, values=("", "", ""), open=True)
|
|
||||||
for vi in sorted(groups[g], key=lambda x: x.name):
|
|
||||||
updated_str = "—"
|
|
||||||
if vi.last_updated:
|
|
||||||
updated_str = datetime.fromtimestamp(vi.last_updated).strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
if vi.error:
|
|
||||||
value_preview = f"[ERR] {vi.error}"
|
|
||||||
else:
|
|
||||||
# Compact preview of first line
|
|
||||||
value_preview = coerce_preview(vi.last_value, 120)
|
|
||||||
|
|
||||||
tag = infer_status_color(vi.last_value if not vi.error else "error")
|
|
||||||
self.tree.insert(
|
|
||||||
parent,
|
|
||||||
"end",
|
|
||||||
text=vi.name,
|
|
||||||
values=(value_preview, updated_str, vi.last_status or "—"),
|
|
||||||
tags=(tag,),
|
|
||||||
)
|
|
||||||
|
|
||||||
# ------------- Shutdown -------------
|
|
||||||
def on_close(self) -> None:
|
|
||||||
self.stop_polling()
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
app = App()
|
|
||||||
app.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Zależności vtt_work — wyłącznie dla help_scripts/sound.py (generator dźwięków).
|
||||||
|
# Pozostałe skrypty Foundry używają czystego stdlib.
|
||||||
|
numpy==2.3.3
|
||||||
|
scipy==1.16.2
|
||||||
|
soundfile==0.13.1
|
||||||
|
cffi==2.0.0
|
||||||
|
pycparser==2.23
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.1
|
0.4
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ Hooks.once("ready", () => {
|
|||||||
window._gkAutoLoadoutHookId = Hooks.on("createItem", async (item, opts, userId) => {
|
window._gkAutoLoadoutHookId = Hooks.on("createItem", async (item, opts, userId) => {
|
||||||
try {
|
try {
|
||||||
if (item?.type !== "archetype") return;
|
if (item?.type !== "archetype") return;
|
||||||
const src = item?.flags?.core?.sourceId || "";
|
// v12+: pochodzenie z paczki przeniesione do _stats.compendiumSource (flags.core.sourceId deprecated)
|
||||||
|
const src = item?._stats?.compendiumSource || item?.flags?.core?.sourceId || "";
|
||||||
const isFromOurPack = src.startsWith(PACK_SRC_PREFIX);
|
const isFromOurPack = src.startsWith(PACK_SRC_PREFIX);
|
||||||
const isOurGKByName = item?.name === "Grey Knight (Archetype)";
|
const isOurGKByName = item?.name === "Grey Knight (Archetype)";
|
||||||
if (!isFromOurPack && !isOurGKByName) return;
|
if (!isFromOurPack && !isOurGKByName) return;
|
||||||
|
|||||||
@@ -21,13 +21,13 @@
|
|||||||
luminosity:0.5, attenuation:0.35, saturation:0, contrast:0, shadows:0,
|
luminosity:0.5, attenuation:0.35, saturation:0, contrast:0, shadows:0,
|
||||||
animation:{ type:"pulse", speed:3, intensity:4, reverse:false } }
|
animation:{ type:"pulse", speed:3, intensity:4, reverse:false } }
|
||||||
});
|
});
|
||||||
await canvas.perception.update({ lighting: { refresh: true } });
|
await canvas.perception.update({ refreshLighting: true });
|
||||||
};
|
};
|
||||||
const setGlowOff = async () => {
|
const setGlowOff = async () => {
|
||||||
const prev = token.document.getFlag(FLAG_SCOPE, FLAG_KEY);
|
const prev = token.document.getFlag(FLAG_SCOPE, FLAG_KEY);
|
||||||
if (prev) { await token.document.update({ light: prev }); await token.document.unsetFlag(FLAG_SCOPE, FLAG_KEY); }
|
if (prev) { await token.document.update({ light: prev }); await token.document.unsetFlag(FLAG_SCOPE, FLAG_KEY); }
|
||||||
else { await token.document.update({ light: { dim:0, bright:0, color:null } }); }
|
else { await token.document.update({ light: { dim:0, bright:0, color:null } }); }
|
||||||
await canvas.perception.update({ lighting: { refresh: true } });
|
await canvas.perception.update({ refreshLighting: true });
|
||||||
};
|
};
|
||||||
if (wasDisabled) { await setGlowOn(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes <span style="color:#66ccff">ENGAGED</span>. (Akcja)` }); }
|
if (wasDisabled) { await setGlowOn(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes <span style="color:#66ccff">ENGAGED</span>. (Akcja)` }); }
|
||||||
else { await setGlowOff(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes DISENGAGED.` }); }
|
else { await setGlowOff(); ChatMessage.create({ content: `<b>Aegis:</b> Warding Runes DISENGAGED.` }); }
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ async function gkResolveActorFromMessage(doc) {
|
|||||||
}
|
}
|
||||||
async function gkIsPsychicMessage(doc) {
|
async function gkIsPsychicMessage(doc) {
|
||||||
try {
|
try {
|
||||||
const raw = `${doc.flavor || ""} ${TextEditor?.stripHTML(doc.content || "") || ""}`.toLowerCase();
|
// v13: TextEditor przeniesiony do foundry.applications.ux (global deprecated) — z fallbackiem na v11/v12
|
||||||
|
const _TE = foundry.applications?.ux?.TextEditor?.implementation ?? globalThis.TextEditor;
|
||||||
|
const raw = `${doc.flavor || ""} ${_TE?.stripHTML?.(doc.content || "") || ""}`.toLowerCase();
|
||||||
const keys = ["psychic","psychic mastery","psychic power","moc psioniczna","smite","hammerhand","sanctuary","shrouding","might of titan"];
|
const keys = ["psychic","psychic mastery","psychic power","moc psioniczna","smite","hammerhand","sanctuary","shrouding","might of titan"];
|
||||||
if (keys.some(k=>raw.includes(k))) return true;
|
if (keys.some(k=>raw.includes(k))) return true;
|
||||||
const flagsStr = JSON.stringify(doc.flags || {}).toLowerCase();
|
const flagsStr = JSON.stringify(doc.flags || {}).toLowerCase();
|
||||||
@@ -105,7 +107,8 @@ Hooks.once("ready", () => {
|
|||||||
const ok = await ensureHasItemByName(actor, "Smite", "psychicPower");
|
const ok = await ensureHasItemByName(actor, "Smite", "psychicPower");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
ui.notifications?.warn("Smite not found in available packs. Please add a Librarius power manually.");
|
ui.notifications?.warn("Smite not found in available packs. Please add a Librarius power manually.");
|
||||||
game.ui?.sidebar?.tabs?.compendium?.activate();
|
// globalny obiekt to `ui`, nie `game.ui`; activateTab działa w v12/v13
|
||||||
|
ui.sidebar?.activateTab?.("compendium");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) { console.error("gk-greyknight — Brotherhood prompt:", e); }
|
} catch (e) { console.error("gk-greyknight — Brotherhood prompt:", e); }
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ Hooks.on("getSceneControlButtons", (controls) => {
|
|||||||
button: true,
|
button: true,
|
||||||
visible: true,
|
visible: true,
|
||||||
order: 999,
|
order: 999,
|
||||||
onClick: () => GKFocusPanel.toggle()
|
onChange: () => GKFocusPanel.toggle()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
{"name":"Lunar-class Cruiser","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Lunar-class Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":1,"speed":8,"space":70,"power":80},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Archetypal line cruiser—balanced, dependable, easily refitted. Backbone of many squadrons.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":40},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Battlefleet Koronus, p. 24"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Lunar-class Cruiser","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"0b59c38ddda63f42","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Secutor-class Monitor-Cruiser","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Secutor-class Monitor-Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":3,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":1,"speed":8,"space":68,"power":82},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Mechanicus monitor with powerful dorsals and redundant shielding; anchors actions.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":40},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Into the Storm, p. 151"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Secutor-class Monitor-Cruiser","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"0eb071148d375807","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Lathe-class Monitor-Cruiser","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Lathe-class Monitor-Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":3,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":1,"speed":8,"space":68,"power":82},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Lathes-forged variant emphasising resilient sanctified systems.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":40},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Into the Storm, p. 152"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Lathe-class Monitor-Cruiser","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"104965869ffa36bb","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Frigate Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Frigate Hull (Empty)","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":10,"integrity":24,"manoeuvre":1,"detection":1,"speed":10,"space":40,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":1,"notes":"Empty hull. Add Components from Items pack.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":24},"resilience":{"bonus":0,"total":10},"defence":{"bonus":3},"fly":0}}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Frigate Hull (Empty)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"1f954166473bedf3","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Conquest-class Star Galleon","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Conquest-class Star Galleon","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":75,"power":70},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Heavily built merchant galleon with strong batteries and hull.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Battlefleet Koronus, p. 25"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Conquest-class Star Galleon","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"287a7cd15c95031f","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Goliath-class Factory Ship","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Goliath-class Factory Ship","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":75,"power":70},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Roving manufactorum and refinery; long autonomous deployments.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Battlefleet Koronus, p. 30"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Goliath-class Factory Ship","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"2c612305a7bb853f","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Escort Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Escort Hull (Empty)","size":"Escort","slots":{"prow":1,"dorsal":0,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":9,"integrity":18,"manoeuvre":2,"detection":1,"speed":10,"space":35,"power":40},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":2,"notes":"Empty hull. Add Components from Items pack.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":18},"resilience":{"bonus":0,"total":9},"defence":{"bonus":3},"fly":0}}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Escort Hull (Empty)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"4412029d26021383","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Hazeroth-class Privateer","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Hazeroth-class Privateer","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":1,"speed":10,"space":38,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":2,"notes":"Knife-fast raider with reinforced internals and stripped armour. Favoured for ambushes and commerce raiding.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":22},"resilience":{"bonus":0,"total":9},"defence":{"bonus":3},"fly":0},"source":"Rogue Trader Core Rulebook, p. 195"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Hazeroth-class Privateer","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"521f6e630bd813a4","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Tyrant-class Cruiser","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Tyrant-class Cruiser","size":"Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":1,"speed":8,"space":70,"power":80},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Macrobattery-focused cruiser optimised for crushing broadsides.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":40},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Into the Storm, p. 153"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Tyrant-class Cruiser","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"6774443321f24e87","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Universe-class Mass Conveyor","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Universe-class Mass Conveyor","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"},{"tag":"cargo-bay","min":4,"label":"Cargo Holds"},{"tag":"aux-plasma","min":1,"label":"Auxiliary Plasma Generator"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":80,"power":60},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Super-massive hauler for colony loads and crusade materiel; logistics linchpin.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Battlefleet Koronus, p. 31"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Universe-class Mass Conveyor","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"6836e238cce8d9fc","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Cobra-class Destroyer (Renegade)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cobra-class Destroyer (Renegade)","size":"Escort","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":1,"speed":10,"space":36,"power":42},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":2,"notes":"Ex-Navy Cobra in privateer hands with non-standard prow refits.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":22},"resilience":{"bonus":0,"total":9},"defence":{"bonus":3},"fly":0},"source":"Battlefleet Koronus, p. 66"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Cobra-class Destroyer (Renegade)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"6da4418bdbd2206c","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Defiant-class Light Cruiser (Carrier)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Defiant-class Light Cruiser (Carrier)","size":"Light Cruiser","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":1,"speed":9,"space":60,"power":70},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":0,"notes":"Light carrier with significant attack craft; excellent for recon and strikes.","combat":{"size":"colossal","speed":9,"wounds":{"value":0,"bonus":0,"max":32},"resilience":{"bonus":0,"total":11},"defence":{"bonus":2},"fly":0}}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Defiant-class Light Cruiser (Carrier)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"73eed7c83ea60340","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Orion-class Star Clipper","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Orion-class Star Clipper","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":70,"power":70},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Fast passenger/light-cargo clipper for courier missions and risky runs.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Into the Storm, p. 150"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Orion-class Star Clipper","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"750ee1998f698e08","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Vagabond-class Merchant Trader","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Vagabond-class Merchant Trader","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":72,"power":68},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Modular cargobays and adaptable fittings; a chartist workhorse. Simple to keep running in frontier yards; heavily customised.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Rogue Trader Core Rulebook, p. 195"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Vagabond-class Merchant Trader","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"95fcf7b99d705bd1","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Cruiser Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cruiser Hull (Empty)","size":"Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":40,"manoeuvre":-1,"detection":1,"speed":8,"space":70,"power":80},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Empty hull. Add Components from Items pack.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":40},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0}}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Cruiser Hull (Empty)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"96a0dacd77d672bb","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Jericho-class Pilgrim Vessel","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Jericho-class Pilgrim Vessel","size":"Cruiser","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":12,"integrity":36,"manoeuvre":-1,"detection":1,"speed":8,"space":75,"power":68},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":-1,"notes":"Immense conversions for pilgrim passages and reliquary cargo. Cavernous holds and chapels; range and capacity over armour and speed.","combat":{"size":"colossal","speed":8,"wounds":{"value":0,"bonus":0,"max":36},"resilience":{"bonus":0,"total":12},"defence":{"bonus":2},"fly":0},"source":"Rogue Trader Core Rulebook, p. 195"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Jericho-class Pilgrim Vessel","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"a08b1d9ef251ec0b","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Cobra-class Destroyer","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Cobra-class Destroyer","size":"Escort","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":1,"speed":10,"space":36,"power":42},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":2,"notes":"Torpedo raider hunting in packs; strikes, then disengages to reload.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":22},"resilience":{"bonus":0,"total":9},"defence":{"bonus":3},"fly":0},"source":"Battlefleet Koronus, p. 66"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Cobra-class Destroyer","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"b518e981d69324aa","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Havoc-class Merchant Raider","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Havoc-class Merchant Raider","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":9,"integrity":22,"manoeuvre":2,"detection":1,"speed":10,"space":38,"power":45},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":2,"notes":"Hybrid carrier-raider balancing battery weight with cargo. Less protected than Navy hulls but brutal on traffic.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":22},"resilience":{"bonus":0,"total":9},"defence":{"bonus":3},"fly":0},"source":"Rogue Trader Core Rulebook, p. 196"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Havoc-class Merchant Raider","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"c4c69cf1b895c0e1","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Light Cruiser Hull (Empty)","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Light Cruiser Hull (Empty)","size":"Light Cruiser","slots":{"prow":1,"dorsal":1,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":1,"speed":9,"space":60,"power":70},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":0,"notes":"Empty hull. Add Components from Items pack.","combat":{"size":"colossal","speed":9,"wounds":{"value":0,"bonus":0,"max":32},"resilience":{"bonus":0,"total":11},"defence":{"bonus":2},"fly":0}}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Light Cruiser Hull (Empty)","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"c5ca1fc87803b111","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Dauntless-class Light Cruiser","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Dauntless-class Light Cruiser","size":"Light Cruiser","slots":{"prow":1,"dorsal":2,"port":1,"starboard":1,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":11,"integrity":32,"manoeuvre":0,"detection":1,"speed":9,"space":62,"power":72},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":0,"notes":"Versatile patrol cruiser—good acceleration and flexible refits. Often leads Passage Watch patrols.","combat":{"size":"colossal","speed":9,"wounds":{"value":0,"bonus":0,"max":32},"resilience":{"bonus":0,"total":11},"defence":{"bonus":2},"fly":0},"source":"Battlefleet Koronus, p. 36"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Dauntless-class Light Cruiser","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"c8975bb7181d8e12","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Falchion-class Frigate","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Falchion-class Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":1,"speed":10,"space":42,"power":48},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":1,"notes":"Modernised frigate with improved drives for frontier scouting.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":28},"resilience":{"bonus":0,"total":10},"defence":{"bonus":3},"fly":0},"source":"Battlefleet Koronus, p. 28"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Falchion-class Frigate","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"d3853b0270deaf3f","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Sword-class Frigate","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Sword-class Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":1,"speed":10,"space":42,"power":48},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":1,"notes":"Reliable escort with accurate dorsal lasers and stout drives. Squadrons screen convoys and prosecute pirates.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":28},"resilience":{"bonus":0,"total":10},"defence":{"bonus":3},"fly":0},"source":"Battlefleet Koronus, p. 28"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Sword-class Frigate","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"ec53303961ef4fd4","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Turbulent-class Heavy Frigate","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Turbulent-class Heavy Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":1,"speed":10,"space":42,"power":48},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":1,"notes":"Strengthened frame and broader arcs; trades agility for staying power.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":28},"resilience":{"bonus":0,"total":10},"defence":{"bonus":3},"fly":0},"source":"Battlefleet Koronus, p. 28"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Turbulent-class Heavy Frigate","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"f1ea3416a6d0d27e","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
{"name":"Tempest-class Strike Frigate","type":"vehicle","img":"icons/svg/ship.svg","folder":null,"sort":0,"ownership":{"default":0},"flags":{"core":{"sheetClass":"wg-voidships-builder.VoidshipSheet"},"wg-voidships-builder":{"isVoidship":true,"hull":{"class":"Tempest-class Strike Frigate","size":"Frigate","slots":{"prow":1,"dorsal":1,"port":0,"starboard":0,"keel":0},"requirements":[{"tag":"bridge","min":1,"label":"Bridge"},{"tag":"reactor","min":1,"label":"Plasma Reactor"},{"tag":"gellar","min":1,"label":"Gellar Field"},{"tag":"warp","min":1,"label":"Warp Drive"}]},"base":{"armour":10,"integrity":28,"manoeuvre":1,"detection":1,"speed":10,"space":42,"power":48},"crew":{"command":1,"pilot":1,"sensors":1,"engineering":2,"gunnery":2,"security":2,"flightDeck":0,"passengers":0},"rtRaw":{"mnvr":1,"notes":"Close-action brawler for boardings and short-range gunnery. Reinforced prow and over-tuned drives.","combat":{"size":"colossal","speed":10,"wounds":{"value":0,"bonus":0,"max":28},"resilience":{"bonus":0,"total":10},"defence":{"bonus":3},"fly":0},"source":"Rogue Trader Core Rulebook, p. 196"}}},"system":{},"effects":[],"items":[],"prototypeToken":{"name":"Tempest-class Strike Frigate","actorLink":true,"texture":{"src":"icons/svg/ship.svg"},"disposition":1,"displayName":0},"_id":"fb874877eacc7a08","systemId":"wrath-and-glory","systemVersion":"7.1.1"}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{"_id":"BRIDGECLV0000001","name":"Command Bridge (Civilian)","type":"gear","img":"icons/svg/book.svg","flags":{"wg-voidships-builder":{"component":{"kind":"bridge","subtype":"civilian","tags":["bridge","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":1,"detection":1,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Standard command bridge and cogitators.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"REACTORPLSM00001","name":"Plasma Reactor (Civilian)","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"reactor","subtype":"plasma","tags":["reactor","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":4,"power":-10},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":10}}}},"system":{"quantity":1,"rarity":"Common","description":"Primary plasma reactor for voidship operations.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"GELLARSTD0000001","name":"Gellar Field Projector","type":"gear","img":"icons/svg/temple.svg","flags":{"wg-voidships-builder":{"component":{"kind":"gellar","subtype":"standard","tags":["gellar","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":3},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Essential protection in the Immaterium.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"WARPDRVMILO00001","name":"Miloslav Warp Drive (Civilian)","type":"gear","img":"icons/svg/wing.svg","flags":{"wg-voidships-builder":{"component":{"kind":"drive","subtype":"warp","tags":["drive","warp","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":3,"power":6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Reliable civilian warp drive.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"AUGURARRAY000001","name":"Augur Array (Civilian)","type":"gear","img":"icons/svg/mystery-man.svg","flags":{"wg-voidships-builder":{"component":{"kind":"sensor","subtype":"augur","tags":["sensor","auspex","aetherics"]},"voidOnly":true,"voidship":{"slot":"Aetherics","use":{"space":1,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":2,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Standard augur/sensor package.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"HULLPLATE0000001","name":"Reinforced Hull Plating","type":"gear","img":"icons/svg/armor.svg","flags":{"wg-voidships-builder":{"component":{"kind":"armour","subtype":"plating","tags":["hull","armour","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":1,"power":0},"mods":{"armour":2,"integrity":4,"manoeuvre":-1,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Additional armour plates and bracing.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"CARGOHOLD0000001","name":"Cargo Hold & Lighter Bay","type":"gear","img":"icons/svg/crate.svg","flags":{"wg-voidships-builder":{"component":{"kind":"cargo-bay","subtype":"standard","tags":["cargo-bay","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":-5,"power":1},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":-5,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"General-purpose cargo hold with lighter handling.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"AUXPLASMA0000001","name":"Auxiliary Plasma Generator","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"plasma","subtype":"aux-generator","tags":["aux-plasma","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":-6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":6}}}},"system":{"quantity":1,"rarity":"Common","description":"Auxiliary plasma reactor increasing available power.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"MACROPROW0000001","name":"Macrobattery (Prow)","type":"gear","img":"icons/svg/sword.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"macrobattery","tags":["weapon","prow"]},"voidOnly":true,"voidship":{"slot":"Prow","use":{"space":2,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Prow macrobattery.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
{"_id":"MACRODORS0000001","name":"Macrobattery (Dorsal)","type":"gear","img":"icons/svg/sword.svg","flags":{"wg-voidships-builder":{"component":{"kind":"weapon","subtype":"macrobattery","tags":["weapon","dorsal"]},"voidOnly":true,"voidship":{"slot":"Dorsal","use":{"space":2,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Dorsal macrobattery.","value":0,"keywords":[],"effect":"","test":""},"effects":[]}
|
|
||||||
@@ -385,7 +385,8 @@ async _onDropItem(event, data) {
|
|||||||
|
|
||||||
// Register the sheet
|
// Register the sheet
|
||||||
Hooks.once("init", () => {
|
Hooks.once("init", () => {
|
||||||
Actors.registerSheet(VB_MOD, VoidshipSheet, {
|
// v13: kolekcje dokumentów pod foundry.documents.collections (global Actors deprecated) — fallback na v12
|
||||||
|
(foundry.documents?.collections?.Actors ?? Actors).registerSheet(VB_MOD, VoidshipSheet, {
|
||||||
types: ["vehicle"],
|
types: ["vehicle"],
|
||||||
makeDefault: false,
|
makeDefault: false,
|
||||||
label: "Voidship (W&G)"
|
label: "Voidship (W&G)"
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ Hooks.once('init', async () => {
|
|||||||
if (typeof Handlebars?.registerHelper === "function") {
|
if (typeof Handlebars?.registerHelper === "function") {
|
||||||
Handlebars.registerHelper("json", (ctx) => JSON.stringify(ctx ?? {}, null, 2));
|
Handlebars.registerHelper("json", (ctx) => JSON.stringify(ctx ?? {}, null, 2));
|
||||||
}
|
}
|
||||||
await loadTemplates(["modules/wg-voidships-builder/templates/voidship-sheet.hbs"]);
|
// v13: loadTemplates przeniesiony do foundry.applications.handlebars (global deprecated) — fallback na v12
|
||||||
|
const _loadTemplates = foundry.applications?.handlebars?.loadTemplates ?? loadTemplates;
|
||||||
|
await _loadTemplates(["modules/wg-voidships-builder/templates/voidship-sheet.hbs"]);
|
||||||
console.debug("[VB] templates preloaded");
|
console.debug("[VB] templates preloaded");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("wg-voidships-builder: init failed", e);
|
console.warn("wg-voidships-builder: init failed", e);
|
||||||
|
|||||||
Reference in New Issue
Block a user