Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab619f4b73 |
@@ -28,7 +28,8 @@ git clone https://gitea.timevans.uk/slaguru666/RingBRP.git ringbrp
|
||||
```
|
||||
|
||||
Requires **Foundry v13+** (verified against v14.364). No modules are required. Dice So
|
||||
Nice is supported but optional — the system registers four of its own dice textures.
|
||||
Nice works if you have it, using your own dice — the system ships no dice skins and does
|
||||
not touch anybody's dice settings.
|
||||
|
||||
### Deploying to a remote Foundry server
|
||||
|
||||
@@ -47,7 +48,7 @@ packs or regenerate art on that machine:
|
||||
```bash
|
||||
npm install # pulls classic-level, used to write the LevelDB packs
|
||||
npm run build # rebuilds packs/ from tools/content.mjs
|
||||
npm run check # the three guards; the build refuses to run if they fail
|
||||
npm run check # the five guards; the build refuses to run if they fail
|
||||
```
|
||||
|
||||
To update a deployed server: `git pull` and restart Foundry. If the pull touches
|
||||
@@ -152,13 +153,12 @@ template.json data model for every Actor and Item type
|
||||
templates/ Handlebars sheets
|
||||
styles/ one stylesheet
|
||||
lang/en.json all user-facing text
|
||||
icons/ dice/ fonts/ generated art
|
||||
icons/ fonts/ art/ generated art
|
||||
packs/ built LevelDB compendia (committed — see Deploying)
|
||||
tools/
|
||||
content.mjs the catalogue: skills, weapons, armour, gear, vehicles, NPCs
|
||||
build-packs.mjs builds packs/ — runs the three guards first and refuses on failure
|
||||
build-packs.mjs builds packs/ — runs the five guards first and refuses on failure
|
||||
make-icons.mjs draws all 231 icons
|
||||
make-dice-textures.mjs
|
||||
rules-text.mjs generates the rules journal FROM rules.mjs
|
||||
mission.mjs the case generator
|
||||
mission-tables.mjs its content tables
|
||||
@@ -176,11 +176,11 @@ stops being identity-equal to what it aliases, if a constant is re-declared as a
|
||||
literal, or if an exported rule has no spot-check. The packs used to be built under
|
||||
one set of numbers and played under another; this makes that impossible to ship.
|
||||
|
||||
The build runs all three guards before it writes anything:
|
||||
The build runs all five guards before it writes anything:
|
||||
|
||||
<!-- guards:start -->
|
||||
```
|
||||
check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-rules: OK — 87 rules, 20 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct
|
||||
check-lang: OK — en.json, 945 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 46 behavioural tests
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 785 B |
|
Before Width: | Height: | Size: 970 B |
|
Before Width: | Height: | Size: 826 B |
@@ -33,10 +33,9 @@ Only if you intend to edit the catalogue there:
|
||||
|
||||
```bash
|
||||
npm install # classic-level, for writing LevelDB
|
||||
npm run check # the three guards
|
||||
npm run check # the five guards
|
||||
npm run build # rebuilds packs/
|
||||
node tools/make-icons.mjs # redraws all icons
|
||||
node tools/make-dice-textures.mjs # regenerates the Dice So Nice textures
|
||||
```
|
||||
|
||||
`npm run build` runs the guards first and aborts if any fails, so a broken catalogue
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"scripts": {
|
||||
"build": "node tools/build-packs.mjs && node tools/update-readme.mjs",
|
||||
"icons": "node tools/make-icons.mjs",
|
||||
"dice": "node tools/make-dice-textures.mjs",
|
||||
"check": "node tools/check-rules.mjs && node tools/check-kits.mjs && node tools/check-lang.mjs && node tools/check-templates.mjs && node tools/check-behaviour.mjs",
|
||||
"test": "npm run check",
|
||||
"readme": "node tools/update-readme.mjs"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.258824 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.258964 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.259113 175abb000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-20:33:19.259390 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.259432 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.920895 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.921047 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.921223 172817000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-22:44:48.921517 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.921554 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.500947 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.287733 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.281748 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.282130 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.282266 1772df000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-20:33:19.282510 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.282542 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.946376 17382f000 Recovering log #3
|
||||
2026/08/12-22:44:48.946637 17382f000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.946788 17382f000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-22:44:48.947029 17382f000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.947065 17382f000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.577064 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.362147 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.260549 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.260715 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.261020 1772df000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-20:33:19.261257 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.261283 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.922471 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.923346 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.923621 173023000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-22:44:48.923809 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.923836 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.503203 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.290003 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.273026 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.273575 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.273963 175abb000 Level-0 table #5: 45532 bytes OK
|
||||
2026/08/12-20:33:19.274265 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.274316 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.936635 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.937301 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.937794 17200b000 Level-0 table #5: 45532 bytes OK
|
||||
2026/08/12-22:44:48.938219 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.938275 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.557918 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.345401 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.245248 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.246727 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.248205 1762c7000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-20:33:19.248498 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.248546 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.907196 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.908927 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.910392 173023000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-22:44:48.910874 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.910934 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.540045 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.327015 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.255310 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.255554 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.255695 175abb000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-20:33:19.255978 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.256022 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.917345 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.917660 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.917794 172817000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-22:44:48.918046 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.918081 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.569801 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.356982 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.232393 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.235807 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.239193 175abb000 Level-0 table #5: 545293 bytes OK
|
||||
2026/08/12-20:33:19.239934 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.240034 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.896053 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.898901 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.902279 17200b000 Level-0 table #5: 545293 bytes OK
|
||||
2026/08/12-22:44:48.902701 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.902787 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.524869 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.311753 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.263420 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.263582 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.263817 1762c7000 Level-0 table #5: 21613 bytes OK
|
||||
2026/08/12-20:33:19.264044 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.264079 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.925841 17382f000 Recovering log #3
|
||||
2026/08/12-22:44:48.926073 17382f000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.926304 17382f000 Level-0 table #5: 21613 bytes OK
|
||||
2026/08/12-22:44:48.926557 17382f000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.926598 17382f000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.565946 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.353318 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.275275 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.275814 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.276182 175abb000 Level-0 table #5: 43953 bytes OK
|
||||
2026/08/12-20:33:19.276489 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.276529 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.939398 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.940505 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.941454 17200b000 Level-0 table #5: 43953 bytes OK
|
||||
2026/08/12-22:44:48.942198 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.942249 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.562330 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.349699 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.264705 176ad3000 Recovering log #3
|
||||
2026/08/12-20:33:19.265215 176ad3000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.265531 176ad3000 Level-0 table #5: 33484 bytes OK
|
||||
2026/08/12-20:33:19.265770 176ad3000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.265802 176ad3000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.927292 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.927647 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.928175 172817000 Level-0 table #5: 33484 bytes OK
|
||||
2026/08/12-22:44:48.928522 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.928579 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.575038 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.360431 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.251838 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.252367 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.252611 175abb000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-20:33:19.252941 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.252982 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.913614 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.914288 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.914458 172817000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-22:44:48.914739 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.914773 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.494044 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.281949 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.277727 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.278352 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.278612 175abb000 Level-0 table #5: 16902 bytes OK
|
||||
2026/08/12-20:33:19.278891 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.278925 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.943128 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.943292 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.943546 17200b000 Level-0 table #5: 16902 bytes OK
|
||||
2026/08/12-22:44:48.943901 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.943932 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.567928 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.355279 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.268400 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.269480 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.270081 175abb000 Level-0 table #5: 82299 bytes OK
|
||||
2026/08/12-20:33:19.270432 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.270471 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.931234 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.932452 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.933139 17200b000 Level-0 table #5: 82299 bytes OK
|
||||
2026/08/12-22:44:48.933493 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.933538 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.550640 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.337758 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.280144 176ad3000 Recovering log #3
|
||||
2026/08/12-20:33:19.280500 176ad3000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.280605 176ad3000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-20:33:19.280818 176ad3000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.280857 176ad3000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.944808 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.944973 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.945086 172817000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-22:44:48.945317 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.945349 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.544720 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.331276 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.253872 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.254006 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.254155 1762c7000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-20:33:19.254377 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.254415 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.915783 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.915922 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.916069 173023000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-22:44:48.916313 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.916348 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.496700 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.284515 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.262214 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.262355 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.262536 175abb000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-20:33:19.262774 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.262806 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.924550 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.924684 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.924837 17200b000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-22:44:48.925120 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.925163 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.505366 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.292038 16fde7000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.257047 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.257521 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.257788 1772df000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-20:33:19.258070 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.258099 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.918709 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.919008 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.919255 173023000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-22:44:48.919627 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.919674 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.498976 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.286188 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1075,7 +1075,6 @@ export async function skillRoll({
|
||||
}
|
||||
);
|
||||
|
||||
rbDressDice(roll, actor);
|
||||
await roll.toMessage({
|
||||
speaker: speaker ?? ChatMessage.getSpeaker({ actor }),
|
||||
content,
|
||||
@@ -1108,7 +1107,7 @@ export async function majorWoundRoll({ actor, woundType = "blunt", monofilament
|
||||
console.error("Custodians | majorWoundRoll called without an actor");
|
||||
return null;
|
||||
}
|
||||
const r = rbDressDice(await new Roll("1d10").evaluate(), actor);
|
||||
const r = await new Roll("1d10").evaluate();
|
||||
let result = r.total;
|
||||
if (monofilament && result >= 4) result = 10; // spec §3.3 shift
|
||||
else if (blunt && result === 10) result = 9;
|
||||
@@ -1171,7 +1170,7 @@ export async function hitLocationRoll({ target, damage, mode = "ranged",
|
||||
}
|
||||
const sys = target.system;
|
||||
const speciesId = sys.speciesProfile || "baseline";
|
||||
const r = rbDressDice(await new Roll("1d20").evaluate(), target);
|
||||
const r = await new Roll("1d20").evaluate();
|
||||
const loc = locationFor(r.total, speciesId, mode);
|
||||
const live = (sys.locations ?? []).find(l => l.id === loc.id);
|
||||
const max = live?.max ?? locationMaxHp(sys.hp?.max ?? 1, loc.frac);
|
||||
@@ -1280,7 +1279,7 @@ export async function rollReaction(combatant) {
|
||||
: reactionPrintedFrom(rb.base, f.loadOverride);
|
||||
const mod = reactionModifier({ aim: f.aim ?? 0, edge: !!f.edge, drag: !!f.drag });
|
||||
|
||||
const roll = rbDressDice(await new Roll(`1d${RINGBRP.reaction.die}`).evaluate(), actor);
|
||||
const roll = await new Roll(`1d${RINGBRP.reaction.die}`).evaluate();
|
||||
const raw = roll.total;
|
||||
const coherence = actor.system?.coherence?.value ?? 10;
|
||||
const shown = fluxDie(raw, coherence);
|
||||
@@ -2384,7 +2383,7 @@ export async function fall(actor, metres, { soft = false, controlled = false } =
|
||||
content: `<div class="ringbrp-chat">${game.i18n.format("RINGBRP.Fall.Short", { m: metres })}</div>` });
|
||||
return { damage: 0 };
|
||||
}
|
||||
const roll = rbDressDice(await new Roll(plan.formula).evaluate(), actor);
|
||||
const roll = await new Roll(plan.formula).evaluate();
|
||||
await roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor }),
|
||||
flavor: game.i18n.format("RINGBRP.Fall.Flavor", { m: metres, dice: plan.formula }) });
|
||||
// armourApplied true: the fall ignores armour, and this is how we say so.
|
||||
@@ -2417,7 +2416,7 @@ export async function asphyxiate(actor, { held = 0, prepared = true } = {}) {
|
||||
export async function burn(actor, bandId = "clothes", { round = 1 } = {}) {
|
||||
if (!actor) return null;
|
||||
const band = fireBandFor(bandId);
|
||||
const roll = rbDressDice(await new Roll(band.damage).evaluate(), actor);
|
||||
const roll = await new Roll(band.damage).evaluate();
|
||||
// Armour on the round you catch, and never after. A sealed suit is the exception
|
||||
// and the GM should say so by running it as a `spark` that never spreads.
|
||||
const armour = (band.armour && round === 1) ? (Number(actor.system.armourPoints) || 0) : 0;
|
||||
@@ -2436,7 +2435,7 @@ export async function burn(actor, bandId = "clothes", { round = 1 } = {}) {
|
||||
* armour is only ever half of one, at contact.
|
||||
*/
|
||||
export async function detonate({ formula = "4d6", radius = 6, targets = [] } = {}) {
|
||||
const roll = rbDressDice(await new Roll(formula).evaluate(), null);
|
||||
const roll = await new Roll(formula).evaluate();
|
||||
await roll.toMessage({ flavor: game.i18n.format("RINGBRP.Blast.Flavor",
|
||||
{ formula, r: radius }) });
|
||||
const out = [];
|
||||
@@ -4292,7 +4291,7 @@ export async function rollCharacteristicsDialog({ actorName = "" } = {}) {
|
||||
const rollOne = async k => {
|
||||
const cell = el.querySelector(`tr[data-ch="${k}"] .rb-roll-value`);
|
||||
if (!cell || cell.dataset.value) return; // rolled is rolled
|
||||
const roll = rbDressDice(await new Roll(CHARACTERISTIC_DICE[k]).evaluate());
|
||||
const roll = await new Roll(CHARACTERISTIC_DICE[k]).evaluate();
|
||||
await roll.toMessage({
|
||||
flavor: game.i18n.format("RINGBRP.Gen.RolledChar",
|
||||
{ ch: game.i18n.localize(`RINGBRP.Char.${k}`) })
|
||||
@@ -4538,105 +4537,6 @@ Hooks.on("preCreateItem", (item, data) => {
|
||||
* The colourset is chosen per roll from the roller's Coherence, so the dice
|
||||
* themselves report the one number the players are trying not to look at.
|
||||
* ============================================================ */
|
||||
const RB_DICE = {
|
||||
textures: {
|
||||
"custodians-registry": { name: "Registry", file: "custodians-registry.png" },
|
||||
"custodians-drift": { name: "Drift", file: "custodians-drift.png" },
|
||||
"custodians-displaced": { name: "Displaced", file: "custodians-displaced.png" },
|
||||
"custodians-agency": { name: "Agency", file: "custodians-agency.png" }
|
||||
},
|
||||
// one colourset per Coherence band, plus the plain agency set
|
||||
sets: [
|
||||
{ name: "custodians-anchored", description: "Custodians — Anchored",
|
||||
foreground: "#f2ea72", background: "#18268d", outline: "#0c125b",
|
||||
edge: "#6674d8", texture: "custodians-registry", material: "metal" },
|
||||
{ name: "custodians-loose", description: "Custodians — Loose",
|
||||
foreground: "#f2ea72", background: "#16215f", outline: "#0c125b",
|
||||
edge: "#6674d8", texture: "custodians-registry", material: "plastic" },
|
||||
{ name: "custodians-adrift", description: "Custodians — Adrift",
|
||||
foreground: "#ecefee", background: "#141e78", outline: "#0c125b",
|
||||
edge: "#7a6cc8", texture: "custodians-drift", material: "plastic" },
|
||||
{ name: "custodians-unmoored", description: "Custodians — Unmoored",
|
||||
foreground: "#ff7a5c", background: "#101a6e", outline: "#08103f",
|
||||
edge: "#ff7a5c", texture: "custodians-drift", material: "glass" },
|
||||
{ name: "custodians-displaced", description: "Custodians — Displaced",
|
||||
foreground: "#c9b6ff", background: "#0c0e3c", outline: "#000000",
|
||||
edge: "#8a6cc0", texture: "custodians-displaced", material: "glass" },
|
||||
{ name: "custodians-agency", description: "Custodians — Agency issue",
|
||||
foreground: "#f2ea72", background: "#16215f", outline: "#0c125b",
|
||||
edge: "#a8b2e3", texture: "custodians-agency", material: "metal" }
|
||||
]
|
||||
};
|
||||
|
||||
/** Which set an agent rolls, from the band they are in. */
|
||||
export function diceSetFor(actor) {
|
||||
const band = coherenceBand(actor?.system?.coherence?.value ?? 10);
|
||||
return `custodians-${band?.id ?? "anchored"}`;
|
||||
}
|
||||
|
||||
/** Tag a Roll so Dice So Nice draws it in the roller's colours. */
|
||||
function rbDressDice(roll, actor) {
|
||||
if (!roll?.dice?.length) return roll;
|
||||
const colorset = diceSetFor(actor);
|
||||
for (const d of roll.dice) {
|
||||
d.options.colorset = colorset;
|
||||
d.options.appearance = { ...(d.options.appearance ?? {}), colorset };
|
||||
}
|
||||
return roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom Dice So Nice skins are OFF.
|
||||
*
|
||||
* Registering them with "preferred" makes them the default for everybody in the world,
|
||||
* which overrides whatever dice a player had already chosen for themselves — the system
|
||||
* helping itself to a setting that belongs to the user. Turned off at the author's
|
||||
* request; flip this to true to put them back, and everything below still works.
|
||||
*
|
||||
* The textures in dice/ and the generator behind `npm run dice` are left alone. Nothing
|
||||
* reads them while this is false, and deleting work to turn a feature off is a poor
|
||||
* trade.
|
||||
*/
|
||||
const RB_DICE_ENABLED = false;
|
||||
|
||||
Hooks.once("diceSoNiceReady", async (dice3d) => {
|
||||
if (!RB_DICE_ENABLED) return;
|
||||
try {
|
||||
dice3d.addSystem({ id: "custodians", name: "The Custodians" }, "preferred");
|
||||
|
||||
// addTexture returns a Promise and the colourset that references a texture must
|
||||
// not be registered before it resolves, or the die renders untextured.
|
||||
await Promise.all(Object.entries(RB_DICE.textures).map(([id, t]) =>
|
||||
dice3d.addTexture(id, {
|
||||
name: t.name, composite: "source-over",
|
||||
source: `systems/ringbrp/dice/${t.file}`
|
||||
})));
|
||||
|
||||
for (const set of RB_DICE.sets) {
|
||||
dice3d.addColorset({
|
||||
...set, category: "The Custodians",
|
||||
font: "Michroma", visibility: "visible"
|
||||
}, set.name === "custodians-anchored" ? "default" : "showcase");
|
||||
}
|
||||
|
||||
// The percentile pair is the game's core roll, so it is the one worth dressing.
|
||||
dice3d.addDicePreset({
|
||||
type: "d100", system: "custodians",
|
||||
labels: ["00", "10", "20", "30", "40", "50", "60", "70", "80", "90"]
|
||||
});
|
||||
dice3d.addDicePreset({ type: "d10", system: "custodians",
|
||||
labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] });
|
||||
for (const t of ["d4", "d6", "d8", "d12", "d20"]) {
|
||||
const n = Number(t.slice(1));
|
||||
dice3d.addDicePreset({ type: t, system: "custodians",
|
||||
labels: Array.from({ length: n }, (_, i) => String(i + 1)) });
|
||||
}
|
||||
console.log("Custodians | dice registered with Dice So Nice");
|
||||
} catch (err) {
|
||||
console.warn("Custodians | could not register dice", err);
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.on("renderItemDirectory", (app, html) => {
|
||||
const root = html instanceof HTMLElement ? html : html?.[0];
|
||||
if (!root || !game.user.isGM || root.querySelector(".rb-genbar")) return;
|
||||
@@ -5890,7 +5790,6 @@ export async function rollWeaponDamage({ actor = null, weapon, level = "success"
|
||||
roll = await new Roll(usesDm ? formula + dm : formula).evaluate();
|
||||
}
|
||||
|
||||
rbDressDice(roll, actor);
|
||||
const note = level === "critical" ? game.i18n.localize("RINGBRP.Chat.DamageCritical")
|
||||
: level === "special" ? game.i18n.localize("RINGBRP.Chat.DamageSpecial")
|
||||
: "";
|
||||
@@ -6204,7 +6103,7 @@ Hooks.once("init", () => {
|
||||
usePower, grantPower, ARTS, powerCostFor, crossBack,
|
||||
generateMissionCase, missionDialog, salvageFrom,
|
||||
crossingArtPoolFor, crossingGrantsArt, crossingCostFor, returnCrossing,
|
||||
rollWeaponDamage, diceSetFor,
|
||||
rollWeaponDamage,
|
||||
restFor, endOfCase, gearBonusFor,
|
||||
hasTalent, talentAvailable, useTalent, requisition, armCleanScene, resetTalents,
|
||||
generateCharacter, generateNPC, generateDialog, installContent, grantFullSkillList,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "ringbrp",
|
||||
"title": "The Custodians",
|
||||
"description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/**
|
||||
* Dice textures for The Custodians, generated rather than drawn.
|
||||
*
|
||||
* Written as raw RGBA and encoded to PNG with node:zlib, so the repo gains no
|
||||
* image dependency and the textures can be regenerated deterministically. Every
|
||||
* pattern is tileable — Dice So Nice wraps these across a die face, and a seam
|
||||
* shows up immediately on a d20.
|
||||
*
|
||||
* The set is the setting: the Custodians keep a filing system, and the game is
|
||||
* about that filing system being wrong. So the dice carry a ruled grid, and the
|
||||
* further an agent drifts from their own reality the more the grid stops agreeing
|
||||
* with itself.
|
||||
*
|
||||
* node tools/make-dice-textures.mjs
|
||||
*/
|
||||
import { deflateSync } from "node:zlib";
|
||||
import { writeFile, mkdir } from "node:fs/promises";
|
||||
|
||||
const SIZE = 256;
|
||||
|
||||
/* ---------- a minimal PNG encoder: RGBA, no interlace ---------- */
|
||||
function crc32(buf) {
|
||||
let c, table = crc32.table;
|
||||
if (!table) {
|
||||
table = crc32.table = new Int32Array(256);
|
||||
for (let n = 0; n < 256; n++) {
|
||||
c = n;
|
||||
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||
table[n] = c;
|
||||
}
|
||||
}
|
||||
c = -1;
|
||||
for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
||||
return (c ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, data) {
|
||||
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
|
||||
const td = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
||||
const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td));
|
||||
return Buffer.concat([len, td, crc]);
|
||||
}
|
||||
|
||||
function png(width, height, rgba) {
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
|
||||
// each scanline is prefixed with filter byte 0
|
||||
const raw = Buffer.alloc(height * (width * 4 + 1));
|
||||
for (let y = 0; y < height; y++) {
|
||||
raw[y * (width * 4 + 1)] = 0;
|
||||
rgba.copy(raw, y * (width * 4 + 1) + 1, y * width * 4, (y + 1) * width * 4);
|
||||
}
|
||||
return Buffer.concat([
|
||||
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
||||
chunk("IHDR", ihdr),
|
||||
chunk("IDAT", deflateSync(raw, { level: 9 })),
|
||||
chunk("IEND", Buffer.alloc(0))
|
||||
]);
|
||||
}
|
||||
|
||||
/* ---------- deterministic noise, so a rebuild is byte-identical ---------- */
|
||||
let seed = 20260810;
|
||||
const rnd = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296;
|
||||
|
||||
const canvas = () => Buffer.alloc(SIZE * SIZE * 4);
|
||||
const put = (buf, x, y, r, g, b, a = 255) => {
|
||||
const i = ((y & (SIZE - 1)) * SIZE + (x & (SIZE - 1))) * 4;
|
||||
buf[i] = r; buf[i + 1] = g; buf[i + 2] = b; buf[i + 3] = a;
|
||||
};
|
||||
const fill = (buf, r, g, b, a = 255) => {
|
||||
for (let i = 0; i < buf.length; i += 4) { buf[i] = r; buf[i + 1] = g; buf[i + 2] = b; buf[i + 3] = a; }
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
1. REGISTRY — the ruled grid of a filing system, kept true.
|
||||
============================================================ */
|
||||
function registry() {
|
||||
const b = canvas();
|
||||
fill(b, 24, 38, 141); // ultramarine ground
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
for (const step of [32]) {
|
||||
for (let k = 0; k < SIZE; k += step) {
|
||||
put(b, i, k, 102, 116, 216); // --rb-line
|
||||
put(b, k, i, 102, 116, 216);
|
||||
}
|
||||
}
|
||||
for (let k = 16; k < SIZE; k += 32) { // fainter half-lines
|
||||
put(b, i, k, 60, 78, 175);
|
||||
put(b, k, i, 60, 78, 175);
|
||||
}
|
||||
}
|
||||
// registry ticks at the intersections
|
||||
for (let x = 0; x < SIZE; x += 64) for (let y = 0; y < SIZE; y += 64) {
|
||||
for (let d = -3; d <= 3; d++) { put(b, x + d, y, 242, 234, 114); put(b, x, y + d, 242, 234, 114); }
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
2. DRIFT — the same grid, no longer agreeing with itself.
|
||||
Used for agents who have come loose from their own reality.
|
||||
============================================================ */
|
||||
function drift() {
|
||||
const b = canvas();
|
||||
fill(b, 20, 30, 120);
|
||||
const wobble = y => Math.round(6 * Math.sin(y / 14) + 3 * Math.sin(y / 5.5));
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
for (let k = 0; k < SIZE; k += 32) {
|
||||
put(b, i + wobble(i), k, 122, 108, 200);
|
||||
put(b, k, i + wobble(i), 122, 108, 200);
|
||||
}
|
||||
}
|
||||
for (let n = 0; n < 900; n++) { // grain where it has torn
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0;
|
||||
put(b, x, y, 200, 120, 150, 255);
|
||||
}
|
||||
for (let x = 0; x < SIZE; x += 64) for (let y = 0; y < SIZE; y += 64) {
|
||||
const ox = ((rnd() * 7) | 0) - 3, oy = ((rnd() * 7) | 0) - 3;
|
||||
for (let d = -3; d <= 3; d++) { put(b, x + d + ox, y + oy, 255, 122, 92); put(b, x + ox, y + d + oy, 255, 122, 92); }
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
3. DISPLACED — the grid is gone. What is left is not filed.
|
||||
============================================================ */
|
||||
function displaced() {
|
||||
const b = canvas();
|
||||
fill(b, 12, 14, 60);
|
||||
for (let n = 0; n < 5200; n++) {
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0;
|
||||
const v = 40 + ((rnd() * 90) | 0);
|
||||
put(b, x, y, v, v * 0.5, v * 0.7);
|
||||
}
|
||||
// a few surviving fragments of rule, going nowhere
|
||||
for (let n = 0; n < 26; n++) {
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0, len = 6 + ((rnd() * 22) | 0);
|
||||
const horiz = rnd() > 0.5;
|
||||
for (let d = 0; d < len; d++) {
|
||||
if (horiz) put(b, x + d, y, 150, 120, 190); else put(b, x, y + d, 150, 120, 190);
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
4. AGENCY — brushed plate, for the agency-issue set.
|
||||
============================================================ */
|
||||
function agency() {
|
||||
const b = canvas();
|
||||
for (let y = 0; y < SIZE; y++) {
|
||||
for (let x = 0; x < SIZE; x++) {
|
||||
const grain = 6 * Math.sin(x / 1.7 + y / 90) + (rnd() * 7 - 3.5);
|
||||
const v = 30 + grain;
|
||||
put(b, x, y, 22 + v, 33 + v, 95 + v);
|
||||
}
|
||||
}
|
||||
for (let k = 0; k < SIZE; k += 48) for (let i = 0; i < SIZE; i++) put(b, i, k, 90, 104, 190);
|
||||
return b;
|
||||
}
|
||||
|
||||
const TEXTURES = {
|
||||
"custodians-registry": registry,
|
||||
"custodians-drift": drift,
|
||||
"custodians-displaced": displaced,
|
||||
"custodians-agency": agency
|
||||
};
|
||||
|
||||
await mkdir(new URL("../dice/", import.meta.url), { recursive: true });
|
||||
for (const [name, make] of Object.entries(TEXTURES)) {
|
||||
const buf = png(SIZE, SIZE, make());
|
||||
await writeFile(new URL(`../dice/${name}.png`, import.meta.url), buf);
|
||||
console.log(` ${name}.png ${SIZE}x${SIZE} ${(buf.length / 1024).toFixed(1)} kB`);
|
||||
}
|
||||
console.log("Custodians | dice textures written");
|
||||