Compare commits

...
111 Commits
Author SHA1 Message Date
Lukas Obermann 6fc36c131e first changes and cleanup 2025-12-08 15:45:05 +01:00
Lukas Obermann 5cd59ca6ce feat: add and adjust translation strings 2025-12-08 15:35:28 +01:00
Lukas Obermann 075182370c fix: fixed blessing range without values 2025-12-08 15:26:28 +01:00
Lukas Obermann d2b8a4450d fix: double name 2025-12-08 15:25:54 +01:00
Lukas Obermann e42a15c483 build(release): 0.24.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-05 14:36:38 +01:00
Lukas Obermann 08a7d8d2c8 fix!: profession prerequisites can differ per profession version
BREAKING CHANGE: The prerequisites field is moved from profession
versions to profession packages.
2025-12-05 14:36:31 +01:00
Lukas Obermann d1c9795e83 fix: arcane traditions and elements can be requirable select options 2025-12-05 14:34:18 +01:00
Lukas Obermann 591a36e914 build(release): 0.23.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-04 21:26:06 +01:00
Lukas Obermann 9392990109 fix: use correct identifiers in restrictions 2025-12-04 21:26:01 +01:00
Lukas Obermann 1961c57a97 build(release): 0.23.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-04 21:22:03 +01:00
Lukas Obermann d4b8ad3bd8 fix: restore removed magical artifact translation properties
This reverts the previous breaking change of removing these properties.
2025-12-04 21:21:57 +01:00
Lukas Obermann 514653be04 build(release): 0.23.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-04 21:05:27 +01:00
Lukas Obermann bd6fc3c144 feat: add translatable intoxicant addiction chance
Closes #71
2025-12-04 21:05:00 +01:00
Lukas Obermann 772da37386 fix(gen)!: duplicate translation postfix 2025-12-04 21:01:18 +01:00
Lukas Obermann a7988fc426 feat!: use description, effect and cost for magical artifact
BREAKING CHANGE: This removes previous note and rules fields.

Closes #74
2025-12-04 20:57:11 +01:00
Lukas Obermann 6958dd7f7b feat!: add map and permanent cost options to magical signs
Closes #75
2025-12-04 20:53:01 +01:00
Lukas Obermann d2418f4036 feat!: add item restrictions to equipment of blessed ones
Closes #72
2025-12-04 20:41:11 +01:00
Lukas Obermann 6a14e811fe feat: add item restrictions to armor
Closes #68
2025-12-04 20:40:56 +01:00
Lukas Obermann 5611ba5440 feat!: unify item restrictions and add profession restrictions
Closes #51
2025-12-04 20:39:02 +01:00
Lukas Obermann fcb30ba06b feat: add expression-based poison duration option
Closes #70
2025-12-04 20:10:50 +01:00
Lukas Obermann 90d3adc6f4 feat: optional intoxicant overdose
Closes #69
2025-12-04 20:02:18 +01:00
Lukas Obermann 62818120f9 feat: add structure points option to some item categories
Closes #76
2025-12-04 19:58:00 +01:00
Lukas Obermann 90ca20f52f feat: allow penalty for combat style special abilities
Closes #77
2025-12-04 19:56:10 +01:00
Lukas Obermann 0da2984acf build(gen): infer translation parameters as union 2025-12-04 19:52:00 +01:00
Lukas Obermann b651562db1 feat!: translation keys follow Unicode MessageFormat 2.0 2025-12-04 19:51:27 +01:00
Lukas Obermann 0f1082cdf5 build(deps): upgrade tsondb to 0.12.9 2025-12-04 19:50:43 +01:00
Lukas Obermann ffe04f0859 build(release): 0.22.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-01 15:41:28 +01:00
Lukas Obermann 80b726f945 feat: ap value may depend on (any) active instance
Closes #49
2025-12-01 15:32:11 +01:00
Lukas Obermann dfd83bd28b feat: support levels for automatic entries 2025-12-01 15:29:22 +01:00
Lukas Obermann a3e35594cc feat: enable automatic entries for advantages 2025-12-01 15:29:02 +01:00
Lukas Obermann 5d5db60ce3 feat: make magical sign property optional
Fixes #52
2025-12-01 14:10:01 +01:00
Lukas Obermann 782ae60ca8 fix: identifier access error
CI reported "Cannot access 'AspectIdentifier' before initialization".
2025-12-01 14:09:19 +01:00
Lukas Obermann f8e53e7dc3 feat: allow magical signs having AE cost
Fixes #53
2025-12-01 14:02:49 +01:00
Lukas Obermann 699fab08d3 refactor: split _Activatable file into multiple 2025-12-01 13:55:37 +01:00
Lukas Obermann 43b86dbf6b feat!: ap value per level based on size category
AP Value per level based on the size category of the character in
addition to values by level.

Closes #43
BREAKING CHANGE: Previous lists of values by level need to be wrapped in
an object.
2025-12-01 13:13:40 +01:00
Lukas Obermann 92d9269a92 feat: add two-handed option to combat technique restriction
Closes #67
2025-12-01 10:54:34 +01:00
Lukas Obermann 3858cec872 refactor: extract combat-related types
Extract types for combat-related special abilities into their own file.
2025-12-01 10:52:43 +01:00
Lukas Obermann 6d3e560394 build(release): 0.21.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-30 21:40:57 +01:00
Lukas Obermann a2fdcdb06e build: add linting and formatting
Add configuration and CI checks as well as fix any linting and
formatting issues reported.
2025-11-30 21:27:30 +01:00
Lukas Obermann 9d2dc029c2 feat: allow three fraction digits for item weight 2025-11-30 16:51:17 +01:00
Lukas Obermann fbde6692ed build(deps): upgrade tsondb to 0.12.6 2025-11-30 16:51:00 +01:00
Lukas Obermann bb4a906cdf ci: remove NODE_AUTH_TOKEN in favor of OIDC 2025-11-27 19:54:06 +01:00
Lukas Obermann c4cb19947c ci: add test workflow to check schema integrity 2025-11-27 19:51:59 +01:00
Lukas Obermann 31ea4894e8 build(release): 0.21.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-27 19:43:01 +01:00
Lukas Obermann da69a793a6 feat: add trade secret option for diseases 2025-11-27 19:41:37 +01:00
Lukas Obermann 64e9770a7b build(deps): upgrade all dependencies 2025-11-27 19:41:22 +01:00
Lukas Obermann bfbf0c017f feat: add weight and complexity options to vehicles
Closes #59
2025-11-27 10:48:59 +01:00
Lukas Obermann 0a86d7396b feat: magical artifacts may define complexity and combat uses
Closes #62
2025-11-27 09:52:21 +01:00
Lukas Obermann fb34438161 fix: ranged weapons may have a range of 0
Fixes #63
2025-11-27 09:50:25 +01:00
Lukas Obermann 6d0ad1e9db build(release): 0.21.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-21 21:54:51 +01:00
Lukas Obermann 8d14f891da feat: allow trade secret for animal poisons 2025-11-21 21:54:45 +01:00
Lukas Obermann 68b857afbe feat!: patrons also work with tribes instead of plain cultures to align with animist powers
This is needed for orcs.

Closes #47
2025-11-21 21:51:23 +01:00
Lukas Obermann 146723c3e7 feat: allow race prerequisite for personality traits
Fixes #45
2025-11-21 21:23:11 +01:00
Lukas Obermann 06b5443531 fix: always display child instances of activatable entries
Fixes #51
2025-11-21 21:15:05 +01:00
Lukas Obermann 490a4ebeeb fix: cantrip note translations should be optional 2025-11-21 18:08:33 +01:00
Lukas Obermann 618d60d538 docs: clarify master of aspect suffix property 2025-11-21 18:08:17 +01:00
Lukas Obermann 4cde8a5482 build(release): 0.20.6
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-16 18:35:39 +01:00
Lukas Obermann 797825b6da feat: focus rules and optional rules can require other focus rules or optional rules to be active 2025-11-16 18:35:30 +01:00
Lukas Obermann d7065f2e9e build(release): 0.20.5
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-15 21:17:23 +01:00
Lukas Obermann 15b7d13c1c feat: include translations in locales 2025-11-15 21:17:13 +01:00
Lukas Obermann 92555ec91e build(release): 0.20.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-15 15:03:06 +01:00
Lukas Obermann ace11a4eea feat: generated typescript typings 2025-11-15 15:02:52 +01:00
Lukas Obermann 9d9c421846 build(release): 0.20.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-14 15:52:00 +01:00
Lukas Obermann f76c622545 fix: enhancement display name 2025-11-14 15:45:50 +01:00
Lukas Obermann dec9a69d7e build(release): 0.20.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-13 21:12:04 +01:00
Lukas Obermann 278f214498 fix: wrong newspaper translation name 2025-11-13 21:12:00 +01:00
Lukas Obermann 58bf76e352 build(release): 0.20.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-13 21:09:08 +01:00
Lukas Obermann 23d429466a feat: add "various" case to armor complexity
Closes #21
2025-11-13 20:55:14 +01:00
Lukas Obermann 2348130ce2 feat: add supplemental content option to magical traditions to enable trailing tables
Closes elyukai/optolith-data#791
2025-11-13 20:52:21 +01:00
Lukas Obermann a2fb493d25 feat: animal poison level may depend on the subtype of animal
Closes #38
2025-11-10 16:15:41 +01:00
Lukas Obermann 7ba241cce5 refactor: simplify imports in main 2025-11-10 16:08:08 +01:00
Lukas Obermann 6503ac161b feat: add newspaper entity
Closes #40
2025-11-10 16:07:53 +01:00
Lukas Obermann f2243b2f68 build(release): 0.20.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-11-02 19:58:16 +01:00
Lukas Obermann 5848215674 docs: use appropriate example for indefinite book cost variant description 2025-11-02 13:29:28 +01:00
Lukas Obermann 79c4daa3d5 fix: make definite book cost variant translations optional if label is not used 2025-11-02 13:28:22 +01:00
Lukas Obermann b89bda8e67 fix!: align book type/category with official sources 2025-11-02 13:27:42 +01:00
Lukas Obermann e3f91381fe fix!: use book cost variant with single single costs as well to enable label usage 2025-11-02 12:52:22 +01:00
Lukas Obermann 47b308b4b9 fix: general select option display name customizer 2025-11-02 11:34:51 +01:00
Lukas Obermann b3a88fa9ea feat: add book category 2025-11-02 11:28:03 +01:00
Lukas Obermann 85f83c3fad build(release): 0.19.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-27 22:55:42 +01:00
Lukas Obermann 5952ef214d fix: general special ability options and activatable instances in general should use child entity types instead of reference types 2025-10-27 22:21:21 +01:00
Lukas Obermann 73166944a1 build(release): 0.19.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-27 21:08:52 +01:00
Lukas Obermann f11c1e1e07 feat!: combineable binary math operators to specific adventure points value by active instances instead of fixed functions 2025-10-27 21:08:37 +01:00
Lukas Obermann 6dd5814b9e build(release): 0.18.13
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-17 14:09:46 +02:00
Lukas Obermann 7898b2da78 feat: add options for adventure point multiplier expression 2025-10-17 14:09:26 +02:00
Lukas Obermann 70621f4755 build(release): 0.18.12
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-17 13:26:37 +02:00
Lukas Obermann 4e60943174 fix: allow single range as a possible book cost value 2025-10-17 13:26:29 +02:00
Lukas Obermann 6f42387571 build(release): 0.18.11
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-17 13:21:39 +02:00
Lukas Obermann 373538121f fix: duplicate DefiniteBookCostVariant identifier 2025-10-17 13:21:15 +02:00
Lukas Obermann 10c0b46389 build(release): 0.18.10
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-17 13:19:43 +02:00
Lukas Obermann bd4507d250 feat: books may have multiple cost values dependending on the edition or other factors 2025-10-17 13:19:31 +02:00
Lukas Obermann c575ec11f7 feat: trade secrets and activatable entries can have an adventure points value that is based on the number of active instances 2025-10-17 10:19:07 +02:00
Lukas Obermann 48e5d3f8c6 feat: trade secrets can have explicit select options 2025-10-17 10:18:23 +02:00
Lukas Obermann f8f4f99a43 build(deps): upgrade tsondb to 0.10.1 and @types/node to 24.8.1 2025-10-17 09:35:44 +02:00
Lukas Obermann fabb70af03 build(release): 0.18.9
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-15 13:08:49 +02:00
Lukas Obermann 034a2ed684 feat: add alternative rule and player type entities 2025-10-15 13:08:45 +02:00
Lukas Obermann 1ddb0ea0cc build(release): 0.18.8
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-14 22:11:01 +02:00
Lukas Obermann 19cf1a8582 build(deps): upgrade tsondb to 0.10.0 2025-10-14 22:10:57 +02:00
Lukas Obermann 5c8740d21f build(release): 0.18.7
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-14 09:25:08 +02:00
Lukas Obermann 1b42e26383 fix: additionalRules have to be optional 2025-10-14 09:25:02 +02:00
Lukas Obermann 99ac5738a2 build(release): 0.18.6
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-14 09:22:38 +02:00
Lukas Obermann a7b1bed0fa feat: add additionalRules to optional rules to separate box text from associated main text 2025-10-14 09:22:30 +02:00
Lukas Obermann 74f127037d build(deps): upgrade tsondb to 0.8.0 2025-10-14 09:21:53 +02:00
Lukas Obermann 01852d16d2 build(release): 0.18.5
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-09 07:27:16 +02:00
Lukas Obermann 8f89936281 build(deps): fix and upgrade tsondb 2025-10-09 07:26:59 +02:00
Lukas Obermann 0bd47d12b5 fix: duplicate identifier violations and non-existent display name 2025-10-08 20:24:07 +02:00
Lukas Obermann 922b05834f refactor: remove translation suffix from translation map declarations 2025-10-08 20:23:10 +02:00
Lukas Obermann a236346ba2 build(release): 0.18.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-05 15:03:21 +02:00
Lukas Obermann e609e61ec3 build(deps): upgrade tsondb to 0.7.5 2025-10-05 15:03:13 +02:00
Lukas Obermann dc0f80ee94 build(release): 0.18.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-04 22:11:06 +02:00
Lukas Obermann b3f734484b fix: include new equipment entities top-level in schema 2025-10-04 21:36:02 +02:00
187 changed files with 7485 additions and 5470 deletions
+29 -13
View File
@@ -12,13 +12,22 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npm run build
- run: npm test
registry-url: https://registry.npmjs.org/
- name: Install dependencies
run: npm ci
- name: Build TypeScript (1st pass)
run: npm run build
continue-on-error: true
- name: Generate database typings
run: npm run generate
- name: Build TypeScript (2nd pass)
run: npm run build
publish-npm:
needs: build
@@ -27,14 +36,21 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Checkout repository
uses: actions/checkout@v4
- name: Use Node.js 24
uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build
# - run: npm run generate
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- name: Install dependencies
run: npm ci
- name: Build TypeScript (1st pass)
run: npm run build
continue-on-error: true
- name: Generate database typings
run: npm run generate
- name: Build TypeScript (2nd pass)
run: npm run build
- name: Publish package to npm
run: npm publish --provenance --access public
+33
View File
@@ -0,0 +1,33 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [23, 24]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm run lint
- run: npm run format:check
- run: npm test
+1
View File
@@ -1,3 +1,4 @@
gen
node_modules
lib
+273
View File
@@ -2,6 +2,279 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [0.24.0](https://github.com/elyukai/optolith-database-schema/compare/v0.23.2...v0.24.0) (2025-12-05)
### ⚠ BREAKING CHANGES
* The prerequisites field is moved from profession
versions to profession packages.
### Bug Fixes
* arcane traditions and elements can be requirable select options ([d1c9795](https://github.com/elyukai/optolith-database-schema/commit/d1c9795e8378b8f7da69e47491c48df6514661a8))
* profession prerequisites can differ per profession version ([08a7d8d](https://github.com/elyukai/optolith-database-schema/commit/08a7d8d2c88beff69ea7b4d68995b1eb6d2528a6))
## [0.23.2](https://github.com/elyukai/optolith-database-schema/compare/v0.23.1...v0.23.2) (2025-12-04)
### Bug Fixes
* use correct identifiers in restrictions ([9392990](https://github.com/elyukai/optolith-database-schema/commit/9392990109296bc4cfbc5116c8fa5360efd4a02d))
## [0.23.1](https://github.com/elyukai/optolith-database-schema/compare/v0.23.0...v0.23.1) (2025-12-04)
### Bug Fixes
* restore removed magical artifact translation properties ([d4b8ad3](https://github.com/elyukai/optolith-database-schema/commit/d4b8ad3bd854b32d2a6b05d1e79837c210910d4b))
## [0.23.0](https://github.com/elyukai/optolith-database-schema/compare/v0.22.0...v0.23.0) (2025-12-04)
### ⚠ BREAKING CHANGES
* **gen:** duplicate translation postfix
* This removes previous note and rules fields.
* add map and permanent cost options to magical signs
* add item restrictions to equipment of blessed ones
* unify item restrictions and add profession restrictions
* translation keys follow Unicode MessageFormat 2.0
### Features
* add expression-based poison duration option ([fcb30ba](https://github.com/elyukai/optolith-database-schema/commit/fcb30ba06b93924ce9399ea5481b98bd2689b229)), closes [#70](https://github.com/elyukai/optolith-database-schema/issues/70)
* add item restrictions to armor ([6a14e81](https://github.com/elyukai/optolith-database-schema/commit/6a14e811fe71e2f6ade006572d4ba4740cc7b4cd)), closes [#68](https://github.com/elyukai/optolith-database-schema/issues/68)
* add item restrictions to equipment of blessed ones ([d2418f4](https://github.com/elyukai/optolith-database-schema/commit/d2418f4036c52d5cf972208c67ecf1229e883c85)), closes [#72](https://github.com/elyukai/optolith-database-schema/issues/72)
* add map and permanent cost options to magical signs ([6958dd7](https://github.com/elyukai/optolith-database-schema/commit/6958dd7f7bda88027d38ba2a3de75a0c645ff7f7)), closes [#75](https://github.com/elyukai/optolith-database-schema/issues/75)
* add structure points option to some item categories ([6281812](https://github.com/elyukai/optolith-database-schema/commit/62818120f9e849ba7e95f3a68a3f00758fb8a514)), closes [#76](https://github.com/elyukai/optolith-database-schema/issues/76)
* add translatable intoxicant addiction chance ([bd6fc3c](https://github.com/elyukai/optolith-database-schema/commit/bd6fc3c1448c4b6a221603f33197d8b44a743509)), closes [#71](https://github.com/elyukai/optolith-database-schema/issues/71)
* allow penalty for combat style special abilities ([90ca20f](https://github.com/elyukai/optolith-database-schema/commit/90ca20f52f806824325a916b8a89f46070db941a)), closes [#77](https://github.com/elyukai/optolith-database-schema/issues/77)
* optional intoxicant overdose ([90d3adc](https://github.com/elyukai/optolith-database-schema/commit/90d3adc6f4a214f3a4178fb402becc446984005d)), closes [#69](https://github.com/elyukai/optolith-database-schema/issues/69)
* translation keys follow Unicode MessageFormat 2.0 ([b651562](https://github.com/elyukai/optolith-database-schema/commit/b651562db13d1531d160f8d3288485a04f9bfa3e))
* unify item restrictions and add profession restrictions ([5611ba5](https://github.com/elyukai/optolith-database-schema/commit/5611ba54402ce6ac401d40160513b3db70327d8d)), closes [#51](https://github.com/elyukai/optolith-database-schema/issues/51)
* use description, effect and cost for magical artifact ([a7988fc](https://github.com/elyukai/optolith-database-schema/commit/a7988fc426ad76f4b876071b4cde05b11d523212)), closes [#74](https://github.com/elyukai/optolith-database-schema/issues/74)
### Bug Fixes
* **gen:** duplicate translation postfix ([772da37](https://github.com/elyukai/optolith-database-schema/commit/772da37386622cff0a1ab4954a392eeaca1ac72a))
## [0.22.0](https://github.com/elyukai/optolith-database-schema/compare/v0.21.2...v0.22.0) (2025-12-01)
### ⚠ BREAKING CHANGES
* Previous lists of values by level need to be wrapped in
an object.
### Features
* add two-handed option to combat technique restriction ([92d9269](https://github.com/elyukai/optolith-database-schema/commit/92d9269a923ccaa9a99617c34de612947ddbcb88)), closes [#67](https://github.com/elyukai/optolith-database-schema/issues/67)
* allow magical signs having AE cost ([f8e53e7](https://github.com/elyukai/optolith-database-schema/commit/f8e53e7dc3645e19a24cc60f1257ccd33f8579d6)), closes [#53](https://github.com/elyukai/optolith-database-schema/issues/53)
* ap value may depend on (any) active instance ([80b726f](https://github.com/elyukai/optolith-database-schema/commit/80b726f945cd6fe4609dfc0b11195ee3801d6433)), closes [#49](https://github.com/elyukai/optolith-database-schema/issues/49)
* ap value per level based on size category ([43b86db](https://github.com/elyukai/optolith-database-schema/commit/43b86dbf6b6940b6352a69d2a5e9c2f2430e2516)), closes [#43](https://github.com/elyukai/optolith-database-schema/issues/43)
* enable automatic entries for advantages ([a3e3559](https://github.com/elyukai/optolith-database-schema/commit/a3e35594ccaf85a7d10db4425234bdf8adc7bca0))
* make magical sign property optional ([5d5db60](https://github.com/elyukai/optolith-database-schema/commit/5d5db60ce3dba9330ed1b6f63b8ceb9e8b4c393b)), closes [#52](https://github.com/elyukai/optolith-database-schema/issues/52)
* support levels for automatic entries ([dfd83bd](https://github.com/elyukai/optolith-database-schema/commit/dfd83bd28b68bf73df6936d566765f526b1879c4))
### Bug Fixes
* identifier access error ([782ae60](https://github.com/elyukai/optolith-database-schema/commit/782ae60ca8e20f9118448922946735313852fed8))
## [0.21.2](https://github.com/elyukai/optolith-database-schema/compare/v0.21.1...v0.21.2) (2025-11-30)
### Features
* allow three fraction digits for item weight ([9d2dc02](https://github.com/elyukai/optolith-database-schema/commit/9d2dc029c2a07ec3180b4d340ff5ea8afe67fcba))
## [0.21.1](https://github.com/elyukai/optolith-database-schema/compare/v0.21.0...v0.21.1) (2025-11-27)
### Features
* add trade secret option for diseases ([da69a79](https://github.com/elyukai/optolith-database-schema/commit/da69a793a65e7737505c4ff140f6be2132119d6c))
* add weight and complexity options to vehicles ([bfbf0c0](https://github.com/elyukai/optolith-database-schema/commit/bfbf0c017f0e7bd08bf0ebebe189a862796831ce)), closes [#59](https://github.com/elyukai/optolith-database-schema/issues/59)
* magical artifacts may define complexity and combat uses ([0a86d73](https://github.com/elyukai/optolith-database-schema/commit/0a86d7396b2fddd977e214ab8be283dd31353292)), closes [#62](https://github.com/elyukai/optolith-database-schema/issues/62)
### Bug Fixes
* ranged weapons may have a range of 0 ([fb34438](https://github.com/elyukai/optolith-database-schema/commit/fb3443816195b3079362d07c9bb3a261f98609c8)), closes [#63](https://github.com/elyukai/optolith-database-schema/issues/63)
## [0.21.0](https://github.com/elyukai/optolith-database-schema/compare/v0.20.6...v0.21.0) (2025-11-21)
### ⚠ BREAKING CHANGES
* patrons also work with tribes instead of plain cultures to align with animist powers
### Features
* allow race prerequisite for personality traits ([146723c](https://github.com/elyukai/optolith-database-schema/commit/146723c3e7f91e8c4b4e63b61df95c5a497ea117)), closes [#45](https://github.com/elyukai/optolith-database-schema/issues/45)
* allow trade secret for animal poisons ([8d14f89](https://github.com/elyukai/optolith-database-schema/commit/8d14f891dabad75b9ad1d37c98f1bf8b10c74d69))
* patrons also work with tribes instead of plain cultures to align with animist powers ([68b857a](https://github.com/elyukai/optolith-database-schema/commit/68b857afbefb0655a842cd86f5321f155054db7a)), closes [#47](https://github.com/elyukai/optolith-database-schema/issues/47)
### Bug Fixes
* always display child instances of activatable entries ([06b5443](https://github.com/elyukai/optolith-database-schema/commit/06b5443531c5b00fbf4b422aac9e2cdc6ea6ff14)), closes [#51](https://github.com/elyukai/optolith-database-schema/issues/51)
* cantrip note translations should be optional ([490a4eb](https://github.com/elyukai/optolith-database-schema/commit/490a4ebeeb728ec61e83193a6f84cd774625b777))
## [0.20.6](https://github.com/elyukai/optolith-database-schema/compare/v0.20.5...v0.20.6) (2025-11-16)
### Features
* focus rules and optional rules can require other focus rules or optional rules to be active ([797825b](https://github.com/elyukai/optolith-database-schema/commit/797825b6da46517b1f7a9e2f00f34b21c8bae15d))
## [0.20.5](https://github.com/elyukai/optolith-database-schema/compare/v0.20.4...v0.20.5) (2025-11-15)
### Features
* include translations in locales ([15b7d13](https://github.com/elyukai/optolith-database-schema/commit/15b7d13c1c64a62b5c3f6406a980cf6477b211db))
## [0.20.4](https://github.com/elyukai/optolith-database-schema/compare/v0.20.3...v0.20.4) (2025-11-15)
### Features
* generated typescript typings ([ace11a4](https://github.com/elyukai/optolith-database-schema/commit/ace11a4eea93665decc0070267e9a873aa856734))
## [0.20.3](https://github.com/elyukai/optolith-database-schema/compare/v0.20.2...v0.20.3) (2025-11-14)
### Bug Fixes
* enhancement display name ([f76c622](https://github.com/elyukai/optolith-database-schema/commit/f76c6225456d09817bb960ce041dd2bb121047e3))
## [0.20.2](https://github.com/elyukai/optolith-database-schema/compare/v0.20.1...v0.20.2) (2025-11-13)
### Bug Fixes
* wrong newspaper translation name ([278f214](https://github.com/elyukai/optolith-database-schema/commit/278f21449836143440d485ba350a8e60748fc3f1))
## [0.20.1](https://github.com/elyukai/optolith-database-schema/compare/v0.20.0...v0.20.1) (2025-11-13)
### Features
* add "various" case to armor complexity ([23d4294](https://github.com/elyukai/optolith-database-schema/commit/23d429466ac6eb34c8086c63abde93bfec5d354d)), closes [#21](https://github.com/elyukai/optolith-database-schema/issues/21)
* add newspaper entity ([6503ac1](https://github.com/elyukai/optolith-database-schema/commit/6503ac161b00c980695ea94a87d27ebbfcef7674)), closes [#40](https://github.com/elyukai/optolith-database-schema/issues/40)
* add supplemental content option to magical traditions to enable trailing tables ([2348130](https://github.com/elyukai/optolith-database-schema/commit/2348130ce28e6ddde64ff6304ae04d01a00573c8)), closes [elyukai/optolith-data#791](https://github.com/elyukai/optolith-data/issues/791)
* animal poison level may depend on the subtype of animal ([a2fb493](https://github.com/elyukai/optolith-database-schema/commit/a2fb493d251d733c13b1d22034c9b8e917e67d68)), closes [#38](https://github.com/elyukai/optolith-database-schema/issues/38)
## [0.20.0](https://github.com/elyukai/optolith-database-schema/compare/v0.19.1...v0.20.0) (2025-11-02)
### ⚠ BREAKING CHANGES
* align book type/category with official sources
* use book cost variant with single single costs as well to enable label usage
### Features
* add book category ([b3a88fa](https://github.com/elyukai/optolith-database-schema/commit/b3a88fa9ea417930c4c5249869f4153404570393))
### Bug Fixes
* align book type/category with official sources ([b89bda8](https://github.com/elyukai/optolith-database-schema/commit/b89bda8e670d1a46d6a5ea2f094401e458ce62f7))
* general select option display name customizer ([47b308b](https://github.com/elyukai/optolith-database-schema/commit/47b308b4b93deabb83aaec11b4036b9a70350a83))
* make definite book cost variant translations optional if label is not used ([79c4daa](https://github.com/elyukai/optolith-database-schema/commit/79c4daa3d50aa3468f0c72ef6a5736779c421039))
* use book cost variant with single single costs as well to enable label usage ([e3f9138](https://github.com/elyukai/optolith-database-schema/commit/e3f91381fe6d93525366e15c3d4073cda90821b8))
## [0.19.1](https://github.com/elyukai/optolith-database-schema/compare/v0.19.0...v0.19.1) (2025-10-27)
### Bug Fixes
* general special ability options and activatable instances in general should use child entity types instead of reference types ([5952ef2](https://github.com/elyukai/optolith-database-schema/commit/5952ef214d388e328b6a94364384eed712cefb13))
## [0.19.0](https://github.com/elyukai/optolith-database-schema/compare/v0.18.13...v0.19.0) (2025-10-27)
### ⚠ BREAKING CHANGES
* combineable binary math operators to specific adventure points value by active instances instead of fixed functions
### Features
* combineable binary math operators to specific adventure points value by active instances instead of fixed functions ([f11c1e1](https://github.com/elyukai/optolith-database-schema/commit/f11c1e1e07318ed5836d9964f3658335bf07d072))
## [0.18.13](https://github.com/elyukai/optolith-database-schema/compare/v0.18.12...v0.18.13) (2025-10-17)
### Features
* add options for adventure point multiplier expression ([7898b2d](https://github.com/elyukai/optolith-database-schema/commit/7898b2da788e386e74f75b9883638026900b3e95))
## [0.18.12](https://github.com/elyukai/optolith-database-schema/compare/v0.18.11...v0.18.12) (2025-10-17)
### Bug Fixes
* allow single range as a possible book cost value ([4e60943](https://github.com/elyukai/optolith-database-schema/commit/4e60943174213defe3ad006f64b18c359cc86469))
## [0.18.11](https://github.com/elyukai/optolith-database-schema/compare/v0.18.10...v0.18.11) (2025-10-17)
### Bug Fixes
* duplicate DefiniteBookCostVariant identifier ([3735381](https://github.com/elyukai/optolith-database-schema/commit/373538121fecbae58dfb02d782d23bbdc9a17029))
## [0.18.10](https://github.com/elyukai/optolith-database-schema/compare/v0.18.9...v0.18.10) (2025-10-17)
### Features
* books may have multiple cost values dependending on the edition or other factors ([bd4507d](https://github.com/elyukai/optolith-database-schema/commit/bd4507d2507e01e363dad831a50f87e691ed6087))
* trade secrets and activatable entries can have an adventure points value that is based on the number of active instances ([c575ec1](https://github.com/elyukai/optolith-database-schema/commit/c575ec11f7326902348956971bea73cf7e9b2cb9))
* trade secrets can have explicit select options ([48e5d3f](https://github.com/elyukai/optolith-database-schema/commit/48e5d3f8c688733227a8de5cc629b3878f949fcf))
## [0.18.9](https://github.com/elyukai/optolith-database-schema/compare/v0.18.8...v0.18.9) (2025-10-15)
### Features
* add alternative rule and player type entities ([034a2ed](https://github.com/elyukai/optolith-database-schema/commit/034a2ed6849a5581951fe1757c5c7179509f76bd))
## [0.18.8](https://github.com/elyukai/optolith-database-schema/compare/v0.18.7...v0.18.8) (2025-10-14)
## [0.18.7](https://github.com/elyukai/optolith-database-schema/compare/v0.18.6...v0.18.7) (2025-10-14)
### Bug Fixes
* additionalRules have to be optional ([1b42e26](https://github.com/elyukai/optolith-database-schema/commit/1b42e26383567195fc328fecedb760990b3ca6aa))
## [0.18.6](https://github.com/elyukai/optolith-database-schema/compare/v0.18.5...v0.18.6) (2025-10-14)
### Features
* add additionalRules to optional rules to separate box text from associated main text ([a7b1bed](https://github.com/elyukai/optolith-database-schema/commit/a7b1bed0fadc3d346b4962e1ba808eb2ac05e670))
## [0.18.5](https://github.com/elyukai/optolith-database-schema/compare/v0.18.4...v0.18.5) (2025-10-09)
### Bug Fixes
* duplicate identifier violations and non-existent display name ([0bd47d1](https://github.com/elyukai/optolith-database-schema/commit/0bd47d12b58cf47f804387443927c0d97acc7ce7))
## [0.18.4](https://github.com/elyukai/optolith-database-schema/compare/v0.18.3...v0.18.4) (2025-10-05)
## [0.18.3](https://github.com/elyukai/optolith-database-schema/compare/v0.18.2...v0.18.3) (2025-10-04)
### Bug Fixes
* include new equipment entities top-level in schema ([b3f7344](https://github.com/elyukai/optolith-database-schema/commit/b3f734484bbc52bea1f0a3542f3fccb1edfe66cf))
## [0.18.2](https://github.com/elyukai/optolith-database-schema/compare/v0.18.1...v0.18.2) (2025-10-04)
+86
View File
@@ -0,0 +1,86 @@
import js from "@eslint/js"
import { defineConfig, globalIgnores } from "eslint/config"
import globals from "globals"
import tseslint from "typescript-eslint"
export default defineConfig([
globalIgnores([
"**/gen/",
"**/lib/",
"src/{cache,config,helpers,rendering}/**/*",
"tsondb.config.{js,mjs,cjs,ts,mts,cts}",
]),
{
files: [
"src/**/*.{js,mjs,cjs,ts,mts,cts}"
],
plugins: {
js,
},
extends: ["js/recommended"],
},
{
files: [
"src/**/*.{js,mjs,cjs,ts,mts,cts}"
],
languageOptions: {
globals: {
...globals.node,
},
},
},
{
ignores: [
"eslint.config.js",
],
extends: tseslint.configs.strictTypeChecked,
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"@typescript-eslint/consistent-type-exports": "error",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-empty-object-type": [
"error",
{
allowInterfaces: "with-single-extends",
},
],
"@typescript-eslint/no-namespace": [
"error",
{
allowDeclarations: true,
},
],
"@typescript-eslint/prefer-promise-reject-errors": [
"error",
{
allowThrowingUnknown: true,
}
],
"@typescript-eslint/no-base-to-string": "off",
"@typescript-eslint/no-array-constructor": "off",
},
},
// {
// files: ["test/**/*.{js,mjs,cjs,ts,mts,cts}"],
// rules: {
// "@typescript-eslint/no-floating-promises": "off",
// }
// },
])
+1428 -89
View File
File diff suppressed because it is too large Load Diff
+16 -6
View File
@@ -1,6 +1,6 @@
{
"name": "optolith-database-schema",
"version": "0.18.2",
"version": "0.24.0",
"description": "Definitions and utilities for the flat-file database of Optolith, a character creation tool for the Pen and Paper RPG “The Dark Eye 5”, and its external integrations into other software.",
"keywords": [
"tde",
@@ -9,6 +9,7 @@
"dasschwarzeauge"
],
"files": [
"gen",
"lib",
"schema",
"AUTHORS",
@@ -17,17 +18,21 @@
"main": "./lib/main.js",
"exports": {
".": "./lib/main.js",
"./errors": "./lib/errors.js",
"./gen": "./gen/types.d.ts",
"./utils": "./lib/utils.js",
"./cache/*": "./lib/cache/*.js",
"./config/*": "./lib/config/*.js",
"./rendering/*": "./lib/rendering/*.js",
"./types": "./lib/types/index.js",
"./types/*": "./lib/types/*.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"generate": "tsondb generate",
"test": "node ./lib/test.js",
"lint": "eslint",
"format": "prettier \"src/{types/**/*.{ts,tsx},*.{ts,tsx}}\" --write",
"format:check": "prettier \"src/{types/**/*.{ts,tsx},*.{ts,tsx}}\" --check",
"release": "commit-and-tag-version",
"release:sign": "commit-and-tag-version --sign --signoff"
},
@@ -35,13 +40,18 @@
"license": "MPL-2.0",
"dependencies": {
"@optolith/helpers": "^0.2.2",
"tsondb": "^0.7.4",
"tsondb": "^0.12.9",
"yaml": "^2.8.1"
},
"devDependencies": {
"@types/node": "^24.6.2",
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"commit-and-tag-version": "^12.6.0",
"typescript": "^5.9.3"
"eslint": "^9.39.1",
"globals": "^16.5.0",
"prettier": "^3.7.3",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.0"
},
"repository": "github:elyukai/optolith-database-schema",
"bugs": {
+61 -107
View File
@@ -1,60 +1,14 @@
import { isNotNullish } from "@optolith/helpers/nullable"
import { mapObject } from "@optolith/helpers/object"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { CacheConfig } from "../cacheConfig.js"
import { TypeMap } from "../config/types.js"
import { ValidResults } from "../main.js"
import { Aspect } from "../types/Aspect.js"
import { Element } from "../types/Element.js"
import { Property } from "../types/Property.js"
import { TargetCategory } from "../types/TargetCategory.js"
import {
ExplicitSelectOption,
SelectOptions,
SkillApplication,
SkillApplications,
SkillUse,
SkillUses,
} from "../types/_Activatable.js"
import {
SelectOptionCategory,
SelectOptionsAdventurePointsValue,
SkillApplicationOrUse,
SkillSelectOptionCategoryPrerequisite,
SpecificFromSkillSelectOptionCategoryCategory,
SpecificTargetCategory,
} from "../types/_ActivatableSelectOptionCategory.js"
import {
CeremonyIdentifier,
CloseCombatTechniqueIdentifier,
LiturgicalChantIdentifier,
RangedCombatTechniqueIdentifier,
RitualIdentifier,
SkillIdentifier,
SpellIdentifier,
} from "../types/_Identifier.js"
import {
ActivatableIdentifier,
CombatTechniqueIdentifier,
SelectOptionIdentifier,
SkillishIdentifier as SkillIdentifierGroup,
} from "../types/_IdentifierGroup.js"
import { ImprovementCost } from "../types/_ImprovementCost.js"
import { LocaleMap } from "../types/_LocaleMap.js"
import { GeneralPrerequisites, PrerequisiteForLevel } from "../types/_Prerequisite.js"
import { Poison } from "../types/equipment/item/Poison.js"
import { GeneralPrerequisiteGroup } from "../types/prerequisites/PrerequisiteGroups.js"
import { Errata } from "../types/source/_Erratum.js"
import { PublicationRefs } from "../types/source/_PublicationRef.js"
import { BlessedTradition } from "../types/specialAbility/BlessedTradition.js"
import { Language } from "../types/specialAbility/sub/Language.js"
import * as Database from "../../gen/types.js"
const PRINCIPLES_ID = 31
const PROPERTY_KNOWLEDGE_ID = 3
const ASPECT_KNOWLEDGE_ID = 1
export type ResolvedSelectOption = {
id: SelectOptionIdentifier
id: Database.RequirableSelectOptionIdentifier
/**
* Sometimes, professions use specific text selections that are not
@@ -99,7 +53,7 @@ export type ResolvedSelectOption = {
*/
ap_value?: number
src?: PublicationRefs
src?: Database.PublicationRefs
/**
* All translations for the entry, identified by IETF language tag (BCP47).
@@ -129,10 +83,10 @@ export type ResolvedSelectOptionTranslation = {
}
const matchesSpecificSkillishIdList = <T>(
id: T,
config: SpecificFromSkillSelectOptionCategoryCategory<{ id: T }>,
equalsId: (a: T, b: T) => boolean
config: Database.SpecificFromSkillSelectOptionCategoryCategory<{ id: T }>,
equalsId: (a: T, b: T) => boolean,
): boolean => {
switch (config.operation) {
switch (config.operation.kind) {
case "Intersection":
return config.list.some(ref => equalsId(ref.id, id))
case "Difference":
@@ -143,8 +97,8 @@ const matchesSpecificSkillishIdList = <T>(
}
const getSkillishPrerequisites = (
ps: SkillSelectOptionCategoryPrerequisite[] | undefined,
id: SkillIdentifierGroup | CombatTechniqueIdentifier
ps: Database.SkillSelectOptionCategoryPrerequisite[] | undefined,
id: SkillishIdentifier | Database.CombatTechniqueIdentifier,
): GeneralPrerequisites | undefined => {
if (ps === undefined) {
return undefined
@@ -190,7 +144,7 @@ const getSkillishPrerequisites = (
const equalsSkillishIdGroup = (
a: SkillIdentifierGroup | CombatTechniqueIdentifier,
b: SkillIdentifierGroup | CombatTechniqueIdentifier
b: SkillIdentifierGroup | CombatTechniqueIdentifier,
): boolean => {
switch (a.tag) {
case "Skill":
@@ -221,7 +175,7 @@ const getApValueForSkillish = (
| SelectOptionsAdventurePointsValue<SkillIdentifierGroup | CombatTechniqueIdentifier>
| undefined,
id: SkillIdentifierGroup | CombatTechniqueIdentifier,
ic: ImprovementCost
ic: ImprovementCost,
): number | undefined => {
if (config === undefined) {
return undefined
@@ -259,7 +213,7 @@ const getApValueForSkillish = (
const convertSkillApplicationOrUse = (
id: SkillIdentifier,
applicationOrUse: SkillApplicationOrUse
applicationOrUse: SkillApplicationOrUse,
): SkillApplication | SkillUse => ({
id: applicationOrUse.id,
skill: { tag: "Single", single: { id } },
@@ -269,7 +223,7 @@ const convertSkillApplicationOrUse = (
const getDerivedSelectOptions = (
selectOptionCategory: SelectOptionCategory,
entryId: ActivatableIdentifier,
database: ValidResults
database: ValidResults,
): ResolvedSelectOption[] => {
switch (selectOptionCategory.tag) {
case "Blessings":
@@ -317,14 +271,14 @@ const getDerivedSelectOptions = (
.toSorted(([_1, a], [_2, b]) => a.size.id - b.size.id)
.map(([id]) => id),
}),
{}
{},
)
return database.animalShapes.map(([_, animalShape]) => {
const path = database.animalShapePaths.find(([id]) => id === animalShape.path.id)?.[1]
const size = database.animalShapeSizes.find(([id]) => id === animalShape.size.id)?.[1]
const pathIndex =
path !== undefined ? pathsWithOrderedIds[path.id]?.indexOf(animalShape.id) ?? -1 : -1
path !== undefined ? (pathsWithOrderedIds[path.id]?.indexOf(animalShape.id) ?? -1) : -1
return {
id: { tag: "AnimalShape", animal_shape: animalShape.id },
prerequisites:
@@ -333,7 +287,7 @@ const getDerivedSelectOptions = (
? database.animalShapePaths
.filter(
([id]) =>
id !== animalShape.path.id && pathsWithOrderedIds[id]?.[0] !== undefined
id !== animalShape.path.id && pathsWithOrderedIds[id]?.[0] !== undefined,
)
.map(([id]) => ({
level: 1,
@@ -445,7 +399,7 @@ const getDerivedSelectOptions = (
translations: mapObject(race.translations, t10n => ({
name: t10n.name,
})),
})
}),
),
...database.cultures.map(
([_, culture]): ResolvedSelectOption => ({
@@ -454,13 +408,13 @@ const getDerivedSelectOptions = (
translations: mapObject(culture.translations, t10n => ({
name: t10n.name,
})),
})
}),
),
]
case "BlessedTraditions": {
const getPrerequisites = (
blessedTradition: BlessedTradition
blessedTradition: BlessedTradition,
): GeneralPrerequisites | undefined => {
if (
selectOptionCategory.blessed_traditions.require_principles &&
@@ -500,7 +454,7 @@ const getDerivedSelectOptions = (
case "Elements": {
const mapToResolvedSelectOption = ([_, element]: [
number,
Element
Element,
]): ResolvedSelectOption => ({
id: { tag: "Element", element: element.id },
translations: mapObject(element.translations, t10n => ({
@@ -511,7 +465,7 @@ const getDerivedSelectOptions = (
if (selectOptionCategory.elements.specific) {
return database.elements
.filter(([id]) =>
selectOptionCategory.elements.specific!.some(ref => ref.id.element === id)
selectOptionCategory.elements.specific!.some(ref => ref.id.element === id),
)
.map(mapToResolvedSelectOption)
}
@@ -650,9 +604,9 @@ const getDerivedSelectOptions = (
? undefined
: {
name: t10n.master_of_aspect_suffix,
}
},
),
})
}),
)
.filter(value => Object.keys(value.translations).length > 0)
}
@@ -759,7 +713,7 @@ const getDerivedSelectOptions = (
const matchesGroupRequirement =
category.skills.groups === undefined ||
category.skills.groups.some(
ref => ref.id.skill_group === skill.group.id.skill_group
ref => ref.id.skill_group === skill.group.id.skill_group,
)
const matchesIdRequirement =
@@ -767,7 +721,7 @@ const getDerivedSelectOptions = (
matchesSpecificSkillishIdList<SkillIdentifier>(
{ tag: "Skill", skill: skill.id },
category.skills.specific,
equalsSkillishIdGroup
equalsSkillishIdGroup,
)
return matchesGroupRequirement && matchesIdRequirement
@@ -777,16 +731,16 @@ const getDerivedSelectOptions = (
return {
id,
skill_uses: category.skills.skill_uses?.map(use =>
convertSkillApplicationOrUse(id, use)
convertSkillApplicationOrUse(id, use),
),
skill_applications: category.skills.skill_applications?.map(use =>
convertSkillApplicationOrUse(id, use)
convertSkillApplicationOrUse(id, use),
),
prerequisites: getSkillishPrerequisites(category.skills.prerequisites, id),
ap_value: getApValueForSkillish(
category.skills.ap_value ?? apValueGen,
id,
skill.improvement_cost
skill.improvement_cost,
),
src: skill.src,
translations: mapObject(skill.translations, t10n => ({
@@ -802,8 +756,8 @@ const getDerivedSelectOptions = (
matchesSpecificSkillishIdList<SpellIdentifier>(
{ tag: "Spell", spell: spell.id },
category.spells.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, spell]): ResolvedSelectOption => {
const id: SpellIdentifier = { tag: "Spell", spell: spell.id }
@@ -825,8 +779,8 @@ const getDerivedSelectOptions = (
matchesSpecificSkillishIdList<RitualIdentifier>(
{ tag: "Ritual", ritual: ritual.id },
category.rituals.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, ritual]): ResolvedSelectOption => {
const id: RitualIdentifier = { tag: "Ritual", ritual: ritual.id }
@@ -848,8 +802,8 @@ const getDerivedSelectOptions = (
matchesSpecificSkillishIdList<LiturgicalChantIdentifier>(
{ tag: "LiturgicalChant", liturgical_chant: liturgicalChant.id },
category.liturgical_chants.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, liturgicalChant]): ResolvedSelectOption => {
const id: LiturgicalChantIdentifier = {
@@ -860,7 +814,7 @@ const getDerivedSelectOptions = (
id,
prerequisites: getSkillishPrerequisites(
category.liturgical_chants.prerequisites,
id
id,
),
ap_value: getApValueForSkillish(apValueGen, id, liturgicalChant.improvement_cost),
src: liturgicalChant.src,
@@ -877,8 +831,8 @@ const getDerivedSelectOptions = (
matchesSpecificSkillishIdList<CeremonyIdentifier>(
{ tag: "Ceremony", ceremony: ceremony.id },
category.ceremonies.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, ceremony]): ResolvedSelectOption => {
const id: CeremonyIdentifier = { tag: "Ceremony", ceremony: ceremony.id }
@@ -914,8 +868,8 @@ const getDerivedSelectOptions = (
close_combat_technique: closeCombatTechnique.id,
},
category.close_combat_techniques.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, closeCombatTechnique]): ResolvedSelectOption => {
const id: CloseCombatTechniqueIdentifier = {
@@ -926,12 +880,12 @@ const getDerivedSelectOptions = (
id,
prerequisites: getSkillishPrerequisites(
category.close_combat_techniques.prerequisites,
id
id,
),
ap_value: getApValueForSkillish(
apValueGen,
id,
closeCombatTechnique.improvement_cost
closeCombatTechnique.improvement_cost,
),
src: closeCombatTechnique.src,
translations: mapObject(closeCombatTechnique.translations, t10n => ({
@@ -950,8 +904,8 @@ const getDerivedSelectOptions = (
ranged_combat_technique: rangedCombatTechnique.id,
},
category.ranged_combat_techniques.specific,
equalsSkillishIdGroup
)
equalsSkillishIdGroup,
),
)
.map(([_, rangedCombatTechnique]): ResolvedSelectOption => {
const id: RangedCombatTechniqueIdentifier = {
@@ -962,12 +916,12 @@ const getDerivedSelectOptions = (
id,
prerequisites: getSkillishPrerequisites(
category.ranged_combat_techniques.prerequisites,
id
id,
),
ap_value: getApValueForSkillish(
apValueGen,
id,
rangedCombatTechnique.improvement_cost
rangedCombatTechnique.improvement_cost,
),
src: rangedCombatTechnique.src,
translations: mapObject(rangedCombatTechnique.translations, t10n => ({
@@ -984,7 +938,7 @@ const getDerivedSelectOptions = (
case "TargetCategories": {
const mapToResolvedSelectOption = (
targetCategory: TargetCategory,
specificTargetCategory?: SpecificTargetCategory
specificTargetCategory?: SpecificTargetCategory,
): ResolvedSelectOption => ({
id: { tag: "TargetCategory", target_category: targetCategory.id },
volume: specificTargetCategory?.volume,
@@ -996,15 +950,15 @@ const getDerivedSelectOptions = (
if (selectOptionCategory.target_categories.list) {
return database.targetCategories
.filter(([id]) =>
selectOptionCategory.target_categories.list!.some(ref => ref.id.target_category === id)
selectOptionCategory.target_categories.list!.some(ref => ref.id.target_category === id),
)
.map(([id, targetCategory]) =>
mapToResolvedSelectOption(
targetCategory,
selectOptionCategory.target_categories.list!.find(
ref => ref.id.target_category === id
)
)
ref => ref.id.target_category === id,
),
),
)
}
@@ -1019,7 +973,7 @@ const getDerivedSelectOptions = (
const joinLocaleMaps = <T, U, V>(
a: LocaleMap<T>,
b: LocaleMap<U>,
join: (a?: T, b?: U) => V
join: (a?: T, b?: U) => V,
): LocaleMap<NonNullable<V>> => {
const combinedLocaleMap: LocaleMap<NonNullable<V>> = {}
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
@@ -1033,7 +987,7 @@ const joinLocaleMaps = <T, U, V>(
const getExplicitSelectOptions = (
explicitSelectOptions: ExplicitSelectOption[],
database: ValidResults
database: ValidResults,
): ResolvedSelectOption[] =>
explicitSelectOptions
.map((explicitSelectOption): ResolvedSelectOption | undefined => {
@@ -1053,16 +1007,16 @@ const getExplicitSelectOptions = (
...explicitSelectOption.skill,
id,
skill_applications: explicitSelectOption.skill.skill_applications?.map(use =>
convertSkillApplicationOrUse(id, use)
convertSkillApplicationOrUse(id, use),
),
skill_uses: explicitSelectOption.skill.skill_uses?.map(use =>
convertSkillApplicationOrUse(id, use)
convertSkillApplicationOrUse(id, use),
),
translations: joinLocaleMaps(
explicitSelectOption.skill.translations ?? {},
skill.translations,
(explicit, base) =>
base === undefined ? undefined : { ...explicit, name: base.name }
base === undefined ? undefined : { ...explicit, name: base.name },
),
}
}
@@ -1071,7 +1025,7 @@ const getExplicitSelectOptions = (
case "CloseCombatTechnique": {
const id = explicitSelectOption.combat_technique.id.close_combat_technique
const closeCombatTechnique = database.closeCombatTechniques.find(
p => p[0] === id
p => p[0] === id,
)?.[1]
if (closeCombatTechnique === undefined) {
return undefined
@@ -1083,14 +1037,14 @@ const getExplicitSelectOptions = (
explicitSelectOption.combat_technique.translations ?? {},
closeCombatTechnique.translations,
(explicit, base) =>
base === undefined ? undefined : { ...explicit, name: base.name }
base === undefined ? undefined : { ...explicit, name: base.name },
),
}
}
case "RangedCombatTechnique": {
const id = explicitSelectOption.combat_technique.id.ranged_combat_technique
const rangedCombatTechnique = database.rangedCombatTechniques.find(
p => p[0] === id
p => p[0] === id,
)?.[1]
if (rangedCombatTechnique === undefined) {
return undefined
@@ -1102,7 +1056,7 @@ const getExplicitSelectOptions = (
explicitSelectOption.combat_technique.translations ?? {},
rangedCombatTechnique.translations,
(explicit, base) =>
base === undefined ? undefined : { ...explicit, name: base.name }
base === undefined ? undefined : { ...explicit, name: base.name },
),
}
}
@@ -1118,7 +1072,7 @@ const getExplicitSelectOptions = (
const getSelectOptions = (
selectOptions: SelectOptions,
id: ActivatableIdentifier,
database: ValidResults
database: ValidResults,
): ResolvedSelectOption[] => [
...(selectOptions.derived === undefined
? []
@@ -1131,7 +1085,7 @@ const getSelectOptions = (
const getSelectOptionsForResults = (
database: ValidResults,
idTag: ActivatableIdentifier["tag"],
results: [id: number, data: { select_options?: SelectOptions }][]
results: [id: number, data: { select_options?: SelectOptions }][],
) =>
results.reduce<{
[id: number]: ResolvedSelectOption[]
@@ -1247,7 +1201,7 @@ const getSelectOptionsForResults = (
return assertExhaustive(idTag)
}
})(),
database
database,
)
if (options.length > 0) {
acc[id] = options
+20 -26
View File
@@ -1,30 +1,24 @@
import type { CacheConfig } from "../cacheConfig.js"
import type { CacheBuilder } from "./cacheConfig.js"
export type AncestorBloodAdvantagesCache = {
ids: number[]
ids: string[]
}
export const config: CacheConfig<AncestorBloodAdvantagesCache> = {
builder(database) {
return {
ids: database.advantages
.filter(([id, advantage]) =>
advantage.prerequisites?.some(p => {
switch (p.prerequisite.tag) {
case "Single":
return p.prerequisite.single.tag === "NoOtherAncestorBloodAdvantage"
case "Disjunction":
return p.prerequisite.disjunction.list.some(
d => d.tag === "NoOtherAncestorBloodAdvantage"
)
case "Group":
return p.prerequisite.group.list.some(
g => g.tag === "NoOtherAncestorBloodAdvantage"
)
}
})
)
.map(([id]) => id),
}
},
}
export const build: CacheBuilder<AncestorBloodAdvantagesCache> = ({ getAllInstances }) => ({
ids: getAllInstances("Advantage")
.filter(([id, advantage]) =>
advantage.prerequisites?.some(p => {
switch (p.prerequisite.kind) {
case "Single":
return p.prerequisite.Single.kind === "NoOtherAncestorBloodAdvantage"
case "Disjunction":
return p.prerequisite.Disjunction.list.some(
d => d.kind === "NoOtherAncestorBloodAdvantage",
)
case "Group":
return p.prerequisite.Group.list.some(g => g.kind === "NoOtherAncestorBloodAdvantage")
}
}),
)
.map(([id]) => id),
})
+8
View File
@@ -0,0 +1,8 @@
import type { ChildEntityMap, EntityMap } from "../../gen/types.js"
import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../utils.js"
export type CacheBuilder<T> = (database: {
getInstanceById: GetInstanceById<keyof EntityMap>
getAllInstances: GetAllInstances<keyof EntityMap>
getAllChildInstancesForParent: GetAllChildInstancesForParent<keyof ChildEntityMap>
}) => T
+67 -114
View File
@@ -1,8 +1,12 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { CacheConfig } from "../cacheConfig.js"
import { ActivatableIdentifier, RatedIdentifier } from "../types/_IdentifierGroup.js"
import { AdvantageDisadvantagePrerequisites } from "../types/_Prerequisite.js"
import { AdvantageDisadvantagePrerequisiteGroup } from "../types/prerequisites/PrerequisiteGroups.js"
import type {
AdvantageDisadvantagePrerequisiteGroup,
AdvantageDisadvantagePrerequisites,
EntityMap,
RatedIdentifier,
} from "../../gen/types.js"
import { stringFromEnumIdentifier, type GetInstanceById } from "../utils.js"
import type { CacheBuilder } from "./cacheConfig.js"
const BLESSED_ID = 12
const SPELLCASTER_ID = 47
@@ -10,18 +14,18 @@ const SPELLCASTER_ID = 47
export type MagicalAndBlessedAdvantagesAndDisadvantagesCache = {
advantages: {
magical: {
ids: number[]
ids: string[]
}
blessed: {
ids: number[]
ids: string[]
}
}
disadvantages: {
magical: {
ids: number[]
ids: string[]
}
blessed: {
ids: number[]
ids: string[]
}
}
}
@@ -38,7 +42,7 @@ const getAdvantageId = (type: "Magical" | "Blessed") => {
}
const isRatedFor = (type: "Magical" | "Blessed", ratedId: RatedIdentifier) => {
switch (ratedId.tag) {
switch (ratedId.kind) {
case "Spell":
case "Ritual":
return type === "Magical"
@@ -58,26 +62,33 @@ const isRatedFor = (type: "Magical" | "Blessed", ratedId: RatedIdentifier) => {
const isPrerequisiteFor = (
type: "Magical" | "Blessed",
prerequisite: AdvantageDisadvantagePrerequisiteGroup,
getById: (
id: ActivatableIdentifier
) => { id: number; prerequisites?: AdvantageDisadvantagePrerequisites } | undefined,
traversedIds: number[]
getInstanceById: GetInstanceById<keyof EntityMap>,
traversedIds: string[],
): boolean => {
switch (prerequisite.tag) {
switch (prerequisite.kind) {
case "Activatable": {
if (
prerequisite.activatable.id.tag === "Advantage" &&
prerequisite.activatable.id.advantage === getAdvantageId(type) &&
prerequisite.activatable.active
prerequisite.Activatable.id.kind === "Advantage" &&
prerequisite.Activatable.id.Advantage === getAdvantageId(type) &&
prerequisite.Activatable.active
) {
return true
}
const entry = getById(prerequisite.activatable.id)
return entry !== undefined && is(type, entry, getById, traversedIds)
const entry = getInstanceById(prerequisite.Activatable.id)
return (
entry !== undefined &&
is(
type,
stringFromEnumIdentifier(prerequisite.Activatable.id),
entry,
getInstanceById,
traversedIds,
)
)
}
case "Rated":
return isRatedFor(type, prerequisite.rated.id)
return isRatedFor(type, prerequisite.Rated.id)
case "CommonSuggestedByRCP":
case "Sex":
case "Race":
@@ -91,7 +102,7 @@ const isPrerequisiteFor = (
case "MagicalTradition":
case "RatedMinimumNumber":
case "RatedSum":
case "ExternalEnhancement":
case "Enhancement":
case "Text":
case "NoOtherAncestorBloodAdvantage":
case "SexualCharacteristic":
@@ -103,31 +114,35 @@ const isPrerequisiteFor = (
const is = (
type: "Magical" | "Blessed",
entry: { id: number; prerequisites?: AdvantageDisadvantagePrerequisites },
getById: (
id: ActivatableIdentifier
) => { id: number; prerequisites?: AdvantageDisadvantagePrerequisites } | undefined,
traversedIds: number[]
entryId: string,
entry: { prerequisites?: AdvantageDisadvantagePrerequisites },
getInstanceById: GetInstanceById<keyof EntityMap>,
traversedIds: string[],
): boolean => {
if (!entry.prerequisites || traversedIds.includes(entry.id)) {
if (!entry.prerequisites || traversedIds.includes(entryId)) {
return false
}
const newTraversedIds = [...traversedIds, entry.id]
const newTraversedIds = [...traversedIds, entryId]
return (
entry.prerequisites !== undefined &&
entry.prerequisites.some(prerequisite => {
switch (prerequisite.prerequisite.tag) {
switch (prerequisite.prerequisite.kind) {
case "Single":
return isPrerequisiteFor(type, prerequisite.prerequisite.single, getById, newTraversedIds)
return isPrerequisiteFor(
type,
prerequisite.prerequisite.Single,
getInstanceById,
newTraversedIds,
)
case "Disjunction":
return prerequisite.prerequisite.disjunction.list.some(p =>
isPrerequisiteFor(type, p, getById, newTraversedIds)
return prerequisite.prerequisite.Disjunction.list.some(p =>
isPrerequisiteFor(type, p, getInstanceById, newTraversedIds),
)
case "Group":
return prerequisite.prerequisite.group.list.some(p =>
isPrerequisiteFor(type, p, getById, newTraversedIds)
return prerequisite.prerequisite.Group.list.some(p =>
isPrerequisiteFor(type, p, getInstanceById, newTraversedIds),
)
default:
return assertExhaustive(prerequisite.prerequisite)
@@ -136,84 +151,22 @@ const is = (
)
}
export const config: CacheConfig<MagicalAndBlessedAdvantagesAndDisadvantagesCache> = {
builder(database) {
const getActivatableById = (id: ActivatableIdentifier) => {
// prettier-ignore
switch (id.tag) {
case "AdvancedCombatSpecialAbility": return database.advancedCombatSpecialAbilities.find(([entryId]) => entryId === id.advanced_combat_special_ability)?.[1]
case "AdvancedKarmaSpecialAbility": return database.advancedKarmaSpecialAbilities.find(([entryId]) => entryId === id.advanced_karma_special_ability)?.[1]
case "AdvancedMagicalSpecialAbility": return database.advancedMagicalSpecialAbilities.find(([entryId]) => entryId === id.advanced_magical_special_ability)?.[1]
case "AdvancedSkillSpecialAbility": return database.advancedSkillSpecialAbilities.find(([entryId]) => entryId === id.advanced_skill_special_ability)?.[1]
case "Advantage": return database.advantages.find(([entryId]) => entryId === id.advantage)?.[1]
case "AncestorGlyph": return database.ancestorGlyphs.find(([entryId]) => entryId === id.ancestor_glyph)?.[1]
case "ArcaneOrbEnchantment": return database.arcaneOrbEnchantments.find(([entryId]) => entryId === id.arcane_orb_enchantment)?.[1]
case "AttireEnchantment": return database.attireEnchantments.find(([entryId]) => entryId === id.attire_enchantment)?.[1]
case "BlessedTradition": return database.blessedTraditions.find(([entryId]) => entryId === id.blessed_tradition)?.[1]
case "BowlEnchantment": return database.bowlEnchantments.find(([entryId]) => entryId === id.bowl_enchantment)?.[1]
case "BrawlingSpecialAbility": return database.brawlingSpecialAbilities.find(([entryId]) => entryId === id.brawling_special_ability)?.[1]
case "CauldronEnchantment": return database.cauldronEnchantments.find(([entryId]) => entryId === id.cauldron_enchantment)?.[1]
case "CeremonialItemSpecialAbility": return database.ceremonialItemSpecialAbilities.find(([entryId]) => entryId === id.ceremonial_item_special_ability)?.[1]
case "ChronicleEnchantment": return database.chronicleEnchantments.find(([entryId]) => entryId === id.chronicle_enchantment)?.[1]
case "CombatSpecialAbility": return database.combatSpecialAbilities.find(([entryId]) => entryId === id.combat_special_ability)?.[1]
case "CombatStyleSpecialAbility": return database.combatStyleSpecialAbilities.find(([entryId]) => entryId === id.combat_style_special_ability)?.[1]
case "CommandSpecialAbility": return database.commandSpecialAbilities.find(([entryId]) => entryId === id.command_special_ability)?.[1]
case "DaggerRitual": return database.daggerRituals.find(([entryId]) => entryId === id.dagger_ritual)?.[1]
case "Disadvantage": return database.disadvantages.find(([entryId]) => entryId === id.disadvantage)?.[1]
case "FamiliarSpecialAbility": return database.familiarSpecialAbilities.find(([entryId]) => entryId === id.familiar_special_ability)?.[1]
case "FatePointSexSpecialAbility": return database.fatePointSexSpecialAbilities.find(([entryId]) => entryId === id.fate_point_sex_special_ability)?.[1]
case "FatePointSpecialAbility": return database.fatePointSpecialAbilities.find(([entryId]) => entryId === id.fate_point_special_ability)?.[1]
case "FoolsHatEnchantment": return database.foolsHatEnchantments.find(([entryId]) => entryId === id.fools_hat_enchantment)?.[1]
case "GeneralSpecialAbility": return database.generalSpecialAbilities.find(([entryId]) => entryId === id.general_special_ability)?.[1]
case "InstrumentEnchantment": return database.instrumentEnchantments.find(([entryId]) => entryId === id.instrument_enchantment)?.[1]
case "KarmaSpecialAbility": return database.karmaSpecialAbilities.find(([entryId]) => entryId === id.karma_special_ability)?.[1]
case "Krallenkettenzauber": return database.krallenkettenzauber.find(([entryId]) => entryId === id.krallenkettenzauber)?.[1]
case "LiturgicalStyleSpecialAbility": return database.liturgicalStyleSpecialAbilities.find(([entryId]) => entryId === id.liturgical_style_special_ability)?.[1]
case "LycantropicGift": return database.lycantropicGifts.find(([entryId]) => entryId === id.lycantropic_gift)?.[1]
case "MagicalSign": return database.magicalSigns.find(([entryId]) => entryId === id.magical_sign)?.[1]
case "MagicalSpecialAbility": return database.magicalSpecialAbilities.find(([entryId]) => entryId === id.magical_special_ability)?.[1]
case "MagicalTradition": return database.magicalTraditions.find(([entryId]) => entryId === id.magical_tradition)?.[1]
case "MagicStyleSpecialAbility": return database.magicStyleSpecialAbilities.find(([entryId]) => entryId === id.magic_style_special_ability)?.[1]
case "OrbEnchantment": return database.orbEnchantments.find(([entryId]) => entryId === id.orb_enchantment)?.[1]
case "PactGift": return database.pactGifts.find(([entryId]) => entryId === id.pact_gift)?.[1]
case "ProtectiveWardingCircleSpecialAbility": return database.protectiveWardingCircleSpecialAbilities.find(([entryId]) => entryId === id.protective_warding_circle_special_ability)?.[1]
case "RingEnchantment": return database.ringEnchantments.find(([entryId]) => entryId === id.ring_enchantment)?.[1]
case "Sermon": return database.sermons.find(([entryId]) => entryId === id.sermon)?.[1]
case "SexSpecialAbility": return database.sexSpecialAbilities.find(([entryId]) => entryId === id.sex_special_ability)?.[1]
case "SickleRitual": return database.sickleRituals.find(([entryId]) => entryId === id.sickle_ritual)?.[1]
case "SikaryanDrainSpecialAbility": return database.sikaryanDrainSpecialAbilities.find(([entryId]) => entryId === id.sikaryan_drain_special_ability)?.[1]
case "SkillStyleSpecialAbility": return database.skillStyleSpecialAbilities.find(([entryId]) => entryId === id.skill_style_special_ability)?.[1]
case "SpellSwordEnchantment": return database.spellSwordEnchantments.find(([entryId]) => entryId === id.spell_sword_enchantment)?.[1]
case "StaffEnchantment": return database.staffEnchantments.find(([entryId]) => entryId === id.staff_enchantment)?.[1]
case "ToyEnchantment": return database.toyEnchantments.find(([entryId]) => entryId === id.toy_enchantment)?.[1]
case "Trinkhornzauber": return database.trinkhornzauber.find(([entryId]) => entryId === id.trinkhornzauber)?.[1]
case "VampiricGift": return database.vampiricGifts.find(([entryId]) => entryId === id.vampiric_gift)?.[1]
case "Vision": return database.visions.find(([entryId]) => entryId === id.vision)?.[1]
case "WandEnchantment": return database.wandEnchantments.find(([entryId]) => entryId === id.wand_enchantment)?.[1]
case "WeaponEnchantment": return database.weaponEnchantments.find(([entryId]) => entryId === id.weapon_enchantment)?.[1]
default:
return assertExhaustive(id)
}
}
// prettier-ignore
return {
advantages: {
magical: {
ids: database.advantages.filter(([_, entry]) => is("Magical", entry, getActivatableById, [])).map(([id]) => id),
},
blessed: {
ids: database.advantages.filter(([_, entry]) => is("Blessed", entry, getActivatableById, [])).map(([id]) => id),
},
},
disadvantages: {
magical: {
ids: database.disadvantages.filter(([_, entry]) => is("Magical", entry, getActivatableById, [])).map(([id]) => id),
},
blessed: {
ids: database.disadvantages.filter(([_, entry]) => is("Blessed", entry, getActivatableById, [])).map(([id]) => id),
},
},
}
// prettier-ignore
export const config: CacheBuilder<MagicalAndBlessedAdvantagesAndDisadvantagesCache> = ({ getAllInstances, getInstanceById }) => ({
advantages: {
magical: {
ids: getAllInstances("Advantage").filter(([id, entry]) => is("Magical", id, entry, getInstanceById, [])).map(([id]) => id),
},
blessed: {
ids: getAllInstances("Advantage").filter(([id, entry]) => is("Blessed", id, entry, getInstanceById, [])).map(([id]) => id),
},
},
}
disadvantages: {
magical: {
ids: getAllInstances("Disadvantage").filter(([id, entry]) => is("Magical", id, entry, getInstanceById, [])).map(([id]) => id),
},
blessed: {
ids: getAllInstances("Disadvantage").filter(([id, entry]) => is("Blessed", id, entry, getInstanceById, [])).map(([id]) => id),
},
},
})
-5
View File
@@ -1,5 +0,0 @@
import { ValidResults } from "./main.js"
export type CacheConfig<T, D extends unknown[] = []> = {
builder: (data: ValidResults, ...deps: D) => T
}
-13
View File
@@ -1,13 +0,0 @@
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
import { JsonSchemaSpec } from "optolith-tsjsonschemamd/renderers/jsonSchema"
const root = join(dirname(fileURLToPath(import.meta.url)), "..", "..")
export const sourceDir = join(root, "src", "types")
export const libDir = join(root, "lib", "types")
export const jsonSchemaDir = join(root, "schema")
export const markdownDir = join(root, "docs", "reference")
export const swiftDir = join(root, "Sources", "OptolithDatabaseSchema", "GeneratedTypes")
export const jsonSchemaSpec: JsonSchemaSpec = "Draft_2019_09"
-63
View File
@@ -1,63 +0,0 @@
import { filterNonNullable } from "@optolith/helpers/array"
import { collator } from "./helpers/i18n.js"
import { TypeValidationError } from "./main.js"
import { IntegrityError } from "./validation/builders/integrity.js"
import { FileNameError } from "./validation/builders/naming.js"
/**
* Options for pretty-printing errors.
*/
export type PrintOptions = {
/**
* Whether to print each error message in a file or just print that a file has
* errors.
* @default false
*/
verbose?: boolean
}
/**
* Pretty-prints an error map.
* @param errorsByFile A map from file paths to the errors that occurred in
* them.
* @param printOptions Configuration options for the printer.
* @returns The pretty-printed error map.
*/
export const printErrors = (
errorsByFile: Record<string, TypeValidationError[]>,
printOptions: PrintOptions = {}
): string => {
const { verbose = false } = printOptions
return Object.entries(errorsByFile)
.sort(([filePathA], [filePathB]) => collator.compare(filePathA, filePathB))
.flatMap(
verbose
? ([filePath, errors]) => filterNonNullable(errors.map(printVerboseError(filePath)))
: ([filePath, errors]) => [
...errors
.filter(
(error): error is IntegrityError | FileNameError =>
error.keyword === "integrity" || error.keyword === "filename"
)
.map(printVerboseError(filePath)),
...(errors.some(error => error.keyword !== "integrity" && error.keyword !== "filename")
? [errorMessageBlock([filePath], "has schema errors")]
: []),
]
)
.join("\n\n")
}
const printVerboseError =
(filePath: string) =>
(error: TypeValidationError): string => {
const pathSegments = [filePath, ...error.instancePath.split("/").slice(1)]
return errorMessageBlock(pathSegments, error.message ?? "")
}
const errorMessageBlock = (path: string[], message: string): string =>
[
...path.map((segment, i) => `${" ".repeat(i * 2)}in "${segment}":`),
`${" ".repeat(path.length * 2)}${message}`,
].join("\n")
-30
View File
@@ -1,30 +0,0 @@
declare global {
interface Array<T> {
/**
* Returns a `Promise` that resolves when all `Promise`s in the array
* resolve and rejects when any of the `Promise`s in the array rejects.
*/
promiseAll<T>(this: Promise<T>[]): Promise<T[]>
/**
* Returns an object from an array of key-value pairs.
*/
objectFromEntries<K extends string, T>(this: [key: K, value: T][]): Record<K, T>
}
}
Object.defineProperty(Array.prototype, "promiseAll", {
configurable: true,
writable: true,
value: function promiseAll<T>(this: Promise<T>[]): Promise<T[]> {
return Promise.all(this)
},
})
Object.defineProperty(Array.prototype, "objectFromEntries", {
configurable: true,
writable: true,
value: function objectFromEntries<T>(this: [string, T][]): { [k: string]: T } {
return Object.fromEntries(this)
},
})
-1
View File
@@ -1 +0,0 @@
export const collator = Intl.Collator(undefined, { numeric: true })
-60
View File
@@ -1,60 +0,0 @@
import { readFile, readdir } from "node:fs/promises"
import { basename, dirname, extname, join } from "node:path"
import YAML from "yaml"
/**
* Recursively reads all files in a directory and its subdirectories.
* @param dirPath The path to the directory to read.
*/
export async function* readDirectoryRec(
dirPath: string,
filterName: (entryName: string) => boolean = () => true
): AsyncGenerator<string> {
const dirEntries = await readdir(dirPath, { withFileTypes: true })
for (const dirEntry of dirEntries) {
if (filterName(dirEntry.name)) {
const dirEntryPath = join(dirPath, dirEntry.name)
if (dirEntry.isDirectory()) {
yield* readDirectoryRec(dirEntryPath)
} else {
yield dirEntryPath
}
}
}
}
/**
* Reads a JSON file and parses its content.
* @param filePath The path to the JSON file.
* @returns The parsed JSON content.
*/
export const readJsonFile = async (filePath: string): Promise<unknown> => {
const fileContent = await readFile(filePath, "utf-8")
return JSON.parse(fileContent)
}
/**
* Reads a YAML file and parses its content.
* @param filePath The path to the YAML file.
* @returns The parsed YAML content.
*/
export const readYamlFile = async (filePath: string): Promise<unknown> => {
const fileContent = await readFile(filePath, "utf-8")
return YAML.parse(fileContent)
}
/**
* Changes the extension of a file path.
* @param path The path to the file.
* @param ext The new extension to use.
* @returns
*/
export const changeFileExtension = (path: string, ext: string) =>
join(dirname(path), basename(path, extname(path)) + ext)
/**
* Checks if a file name represents a hidden file.
* @param name The file name to check.
*/
export const isHiddenFileName = (name: string) => !name.startsWith(".")
-14
View File
@@ -1,14 +0,0 @@
/**
* A path segment descriptor is either a key or an index.
*/
export type PathSegmentDescriptor = { k: string } | { i: number }
/**
* A path descriptor is a list of path segment descriptors.
*/
export type PathDescriptor = PathSegmentDescriptor[]
export const printPathDescriptor = (pathDescriptor: PathDescriptor): string =>
pathDescriptor
.map(segment => "k" in segment ? segment.k : segment.i)
.join("/")
-27
View File
@@ -1,27 +0,0 @@
/**
* Map the second element of a pair using a function that receives both
* elements.
*/
export const mapSecond = <A, B, C>(f: (second: B, first: A) => C) =>
(pair: [A, B]): [A, C] => [pair[0], f(pair[1], pair[0])]
/**
* Map the second element of a pair using a function that receives both
* elements and returns a `Promise`.
*/
export const mapSecondAsync = <A, B, C>(f: (second: B, first: A) => Promise<C>) =>
async (pair: [A, B]): Promise<[A, C]> => [pair[0], await f(pair[1], pair[0])]
/**
* Map a value using a function into the second value of a pair with the
* original value.
*/
export const mapToSecond = <A, B>(f: (value: A) => B) =>
(value: A): [A, B] => [value, f(value)]
/**
* Map a value using a function that returns a `Promise` into the second value
* of a pair with the original value.
*/
export const mapToSecondAsync = <A, B>(f: (value: A) => Promise<B>) =>
async (value: A): Promise<[A, B]> => [value, await f(value)]
+156 -298
View File
@@ -1,306 +1,164 @@
import { Schema } from "tsondb/schema"
import { Advantage } from "./types/Advantage.js"
import { AnimalDisease } from "./types/AnimalDisease.js"
import { AnimalType } from "./types/AnimalType.js"
import { ArcaneBardTradition } from "./types/ArcaneBardTradition.js"
import { ArcaneDancerTradition } from "./types/ArcaneDancerTradition.js"
import { Aspect } from "./types/Aspect.js"
import { Attribute } from "./types/Attribute.js"
import { Blessing } from "./types/Blessing.js"
import { Cantrip } from "./types/Cantrip.js"
import { Ceremony } from "./types/Ceremony.js"
import { CloseCombatTechnique, RangedCombatTechnique } from "./types/CombatTechnique.js"
import { Condition } from "./types/Condition.js"
import { Continent } from "./types/Continent.js"
import { Culture } from "./types/Culture.js"
import { DerivedCharacteristic } from "./types/DerivedCharacteristic.js"
import { Disadvantage } from "./types/Disadvantage.js"
import { Disease } from "./types/Disease.js"
import { Element } from "./types/Element.js"
import { ExperienceLevel } from "./types/ExperienceLevel.js"
import { EyeColor } from "./types/EyeColor.js"
import { FamiliarsTrick } from "./types/FamiliarsTrick.js"
import { HairColor } from "./types/HairColor.js"
import { Curriculum, Guideline } from "./types/Lessons.js"
import { LiturgicalChant } from "./types/LiturgicalChant.js"
import { Locale } from "./types/Locale.js"
import { MetaCondition } from "./types/MetaCondition.js"
import { PactCategory } from "./types/PactCategory.js"
import { Patron } from "./types/Patron.js"
import { PatronCategory } from "./types/PatronCategory.js"
import { PersonalityTrait } from "./types/PersonalityTrait.js"
import { Profession } from "./types/Profession.js"
import { Property } from "./types/Property.js"
import { Race } from "./types/Race.js"
import { Region } from "./types/Region.js"
import { Ritual } from "./types/Ritual.js"
import { Service } from "./types/Service.js"
import { SexPractice } from "./types/SexPractice.js"
import { Skill } from "./types/Skill.js"
import { SkillGroup } from "./types/SkillGroup.js"
import { SkillModificationLevel } from "./types/SkillModificationLevel.js"
import { SocialStatus } from "./types/SocialStatus.js"
import { Spell } from "./types/Spell.js"
import { State } from "./types/State.js"
import { Talisman } from "./types/Talisman.js"
import { TargetCategory } from "./types/TargetCategory.js"
import { EquipmentPackage } from "./types/equipment/EquipmentPackage.js"
import { Ammunition } from "./types/equipment/item/Ammunition.js"
import { Animal } from "./types/equipment/item/Animal.js"
import { AnimalCare } from "./types/equipment/item/AnimalCare.js"
import { Armor } from "./types/equipment/item/Armor.js"
import { BandageOrRemedy } from "./types/equipment/item/BandageOrRemedy.js"
import { Book } from "./types/equipment/item/Book.js"
import { CeremonialItem } from "./types/equipment/item/CeremonialItem.js"
import { Clothes } from "./types/equipment/item/Clothes.js"
import { Container } from "./types/equipment/item/Container.js"
import { Elixir } from "./types/equipment/item/Elixir.js"
import { EquipmentOfBlessedOnes } from "./types/equipment/item/EquipmentOfBlessedOnes.js"
import { GemOrPreciousStone } from "./types/equipment/item/GemOrPreciousStone.js"
import { IlluminationLightSource } from "./types/equipment/item/IlluminationLightSource.js"
import { IlluminationRefillOrSupply } from "./types/equipment/item/IlluminationRefillOrSupply.js"
import { Jewelry } from "./types/equipment/item/Jewelry.js"
import { Liebesspielzeug } from "./types/equipment/item/Liebesspielzeug.js"
import { LuxuryGood } from "./types/equipment/item/LuxuryGood.js"
import { MagicalArtifact } from "./types/equipment/item/MagicalArtifact.js"
import { MusicalInstrument } from "./types/equipment/item/MusicalInstrument.js"
import { OrienteeringAid } from "./types/equipment/item/OrienteeringAid.js"
import { Poison } from "./types/equipment/item/Poison.js"
import { RopeOrChain } from "./types/equipment/item/RopeOrChain.js"
import { Stationery } from "./types/equipment/item/Stationery.js"
import { ThievesTool } from "./types/equipment/item/ThievesTool.js"
import { ToolOfTheTrade } from "./types/equipment/item/ToolOfTheTrade.js"
import { TravelGearOrTool } from "./types/equipment/item/TravelGearOrTool.js"
import { Vehicle } from "./types/equipment/item/Vehicle.js"
import { Weapon } from "./types/equipment/item/Weapon.js"
import { WeaponAccessory } from "./types/equipment/item/WeaponAccessory.js"
import { ArmorType } from "./types/equipment/item/sub/ArmorType.js"
import { Reach } from "./types/equipment/item/sub/Reach.js"
import { AnimistPower } from "./types/magicalActions/AnimistPower.js"
import { Tribe } from "./types/magicalActions/AnimistPower_Tribe.js"
import { Curse } from "./types/magicalActions/Curse.js"
import { DominationRitual } from "./types/magicalActions/DominationRitual.js"
import { ElvenMagicalSong } from "./types/magicalActions/ElvenMagicalSong.js"
import { GeodeRitual } from "./types/magicalActions/GeodeRitual.js"
import { JesterTrick } from "./types/magicalActions/JesterTrick.js"
import { MagicalDance } from "./types/magicalActions/MagicalDance.js"
import { MagicalMelody } from "./types/magicalActions/MagicalMelody.js"
import { MagicalRune } from "./types/magicalActions/MagicalRune.js"
import { ZibiljaRitual } from "./types/magicalActions/ZibiljaRitual.js"
import { CoreRule } from "./types/rule/CoreRule.js"
import { FocusRule } from "./types/rule/FocusRule.js"
import { Subject } from "./types/rule/FocusRule_Subject.js"
import { OptionalRule } from "./types/rule/OptionalRule.js"
import { Publication } from "./types/source/Publication.js"
import { AdvancedCombatSpecialAbility } from "./types/specialAbility/AdvancedCombatSpecialAbility.js"
import { AdvancedKarmaSpecialAbility } from "./types/specialAbility/AdvancedKarmaSpecialAbility.js"
import { AdvancedMagicalSpecialAbility } from "./types/specialAbility/AdvancedMagicalSpecialAbility.js"
import { AdvancedSkillSpecialAbility } from "./types/specialAbility/AdvancedSkillSpecialAbility.js"
import { AncestorGlyph } from "./types/specialAbility/AncestorGlyph.js"
import { BlessedTradition } from "./types/specialAbility/BlessedTradition.js"
import { BrawlingSpecialAbility } from "./types/specialAbility/BrawlingSpecialAbility.js"
import { CeremonialItemSpecialAbility } from "./types/specialAbility/CeremonialItemSpecialAbility.js"
import { CombatSpecialAbility } from "./types/specialAbility/CombatSpecialAbility.js"
import { CombatStyleSpecialAbility } from "./types/specialAbility/CombatStyleSpecialAbility.js"
import { CommandSpecialAbility } from "./types/specialAbility/CommandSpecialAbility.js"
import { FamiliarSpecialAbility } from "./types/specialAbility/FamiliarSpecialAbility.js"
import { FatePointSexSpecialAbility } from "./types/specialAbility/FatePointSexSpecialAbility.js"
import { FatePointSpecialAbility } from "./types/specialAbility/FatePointSpecialAbility.js"
import { GeneralSpecialAbility } from "./types/specialAbility/GeneralSpecialAbility.js"
import { KarmaSpecialAbility } from "./types/specialAbility/KarmaSpecialAbility.js"
import { LiturgicalStyleSpecialAbility } from "./types/specialAbility/LiturgicalStyleSpecialAbility.js"
import { LycantropicGift } from "./types/specialAbility/LycantropicGift.js"
import { MagicStyleSpecialAbility } from "./types/specialAbility/MagicStyleSpecialAbility.js"
import { MagicalSign } from "./types/specialAbility/MagicalSign.js"
import { MagicalSpecialAbility } from "./types/specialAbility/MagicalSpecialAbility.js"
import { MagicalTradition } from "./types/specialAbility/MagicalTradition.js"
import { PactGift } from "./types/specialAbility/PactGift.js"
import { ProtectiveWardingCircleSpecialAbility } from "./types/specialAbility/ProtectiveWardingCircleSpecialAbility.js"
import { Sermon } from "./types/specialAbility/Sermon.js"
import { SexSpecialAbility } from "./types/specialAbility/SexSpecialAbility.js"
import { SikaryanDrainSpecialAbility } from "./types/specialAbility/SikaryanDrainSpecialAbility.js"
import { SkillStyleSpecialAbility } from "./types/specialAbility/SkillStyleSpecialAbility.js"
import { VampiricGift } from "./types/specialAbility/VampiricGift.js"
import { Vision } from "./types/specialAbility/Vision.js"
import { Language } from "./types/specialAbility/sub/Language.js"
import { Script } from "./types/specialAbility/sub/Script.js"
import { TradeSecret } from "./types/specialAbility/sub/TradeSecret.js"
import { ArcaneOrbEnchantment } from "./types/traditionArtifacts/ArcaneOrbEnchantment.js"
import { AttireEnchantment } from "./types/traditionArtifacts/AttireEnchantment.js"
import { BowlEnchantment } from "./types/traditionArtifacts/BowlEnchantment.js"
import { CauldronEnchantment } from "./types/traditionArtifacts/CauldronEnchantment.js"
import { ChronicleEnchantment } from "./types/traditionArtifacts/ChronicleEnchantment.js"
import { DaggerRitual } from "./types/traditionArtifacts/DaggerRitual.js"
import { FoolsHatEnchantment } from "./types/traditionArtifacts/FoolsHatEnchantment.js"
import { InstrumentEnchantment } from "./types/traditionArtifacts/InstrumentEnchantment.js"
import { Krallenkettenzauber } from "./types/traditionArtifacts/Krallenkettenzauber.js"
import { OrbEnchantment } from "./types/traditionArtifacts/OrbEnchantment.js"
import { RingEnchantment } from "./types/traditionArtifacts/RingEnchantment.js"
import { SickleRitual } from "./types/traditionArtifacts/SickleRitual.js"
import { SpellSwordEnchantment } from "./types/traditionArtifacts/SpellSwordEnchantment.js"
import { StaffEnchantment } from "./types/traditionArtifacts/StaffEnchantment.js"
import { ToyEnchantment } from "./types/traditionArtifacts/ToyEnchantment.js"
import { Trinkhornzauber } from "./types/traditionArtifacts/Trinkhornzauber.js"
import { WandEnchantment } from "./types/traditionArtifacts/WandEnchantment.js"
import { WeaponEnchantment } from "./types/traditionArtifacts/WeaponEnchantment.js"
import { AnimalShape } from "./types/traditionArtifacts/sub/AnimalShape.js"
import { AnimalShapePath } from "./types/traditionArtifacts/sub/AnimalShapePath.js"
import { AnimalShapeSize } from "./types/traditionArtifacts/sub/AnimalShapeSize.js"
import { Brew } from "./types/traditionArtifacts/sub/Brew.js"
import * as Types from "./types/index.js"
export const schema = Schema(
[
AdvancedCombatSpecialAbility,
AdvancedKarmaSpecialAbility,
AdvancedMagicalSpecialAbility,
AdvancedSkillSpecialAbility,
Advantage,
Ammunition,
AncestorGlyph,
AnimalCare,
AnimalDisease,
Animal,
AnimalShapePath,
AnimalShape,
AnimalShapeSize,
AnimalType,
AnimistPower,
Tribe,
ArcaneBardTradition,
ArcaneDancerTradition,
ArcaneOrbEnchantment,
Armor,
ArmorType,
Aspect,
AttireEnchantment,
Attribute,
BandageOrRemedy,
BlessedTradition,
Blessing,
Book,
BowlEnchantment,
BrawlingSpecialAbility,
Brew,
Cantrip,
CauldronEnchantment,
CeremonialItem,
CeremonialItemSpecialAbility,
Ceremony,
ChronicleEnchantment,
CloseCombatTechnique,
Clothes,
CombatSpecialAbility,
CombatStyleSpecialAbility,
CommandSpecialAbility,
Condition,
Container,
Continent,
CoreRule,
Culture,
Curse,
DaggerRitual,
DerivedCharacteristic,
Disadvantage,
Disease,
DominationRitual,
Element,
Elixir,
ElvenMagicalSong,
EquipmentOfBlessedOnes,
EquipmentPackage,
ExperienceLevel,
EyeColor,
FamiliarSpecialAbility,
FamiliarsTrick,
FatePointSexSpecialAbility,
FatePointSpecialAbility,
FocusRule,
Subject,
FoolsHatEnchantment,
GemOrPreciousStone,
GeneralSpecialAbility,
GeodeRitual,
HairColor,
IlluminationLightSource,
IlluminationRefillOrSupply,
InstrumentEnchantment,
JesterTrick,
Jewelry,
KarmaSpecialAbility,
Krallenkettenzauber,
Language,
Curriculum,
Guideline,
Liebesspielzeug,
LiturgicalChant,
LiturgicalStyleSpecialAbility,
LuxuryGood,
LycantropicGift,
MagicalArtifact,
MagicalDance,
MagicalMelody,
MagicalRune,
MagicalSign,
MagicalSpecialAbility,
MagicalTradition,
MagicStyleSpecialAbility,
MetaCondition,
MusicalInstrument,
OptionalRule,
OrbEnchantment,
OrienteeringAid,
PactCategory,
PactGift,
PatronCategory,
Patron,
PersonalityTrait,
Poison,
Profession,
Property,
ProtectiveWardingCircleSpecialAbility,
Publication,
Race,
RangedCombatTechnique,
Reach,
Region,
RingEnchantment,
Ritual,
RopeOrChain,
Script,
Sermon,
Service,
SexPractice,
SexSpecialAbility,
SickleRitual,
SikaryanDrainSpecialAbility,
SkillGroup,
SkillModificationLevel,
Skill,
SkillStyleSpecialAbility,
SocialStatus,
Spell,
SpellSwordEnchantment,
StaffEnchantment,
State,
Stationery,
Talisman,
TargetCategory,
ThievesTool,
ToolOfTheTrade,
ToyEnchantment,
TradeSecret,
TravelGearOrTool,
Trinkhornzauber,
VampiricGift,
Vehicle,
Vision,
WandEnchantment,
WeaponAccessory,
WeaponEnchantment,
Weapon,
ZibiljaRitual,
Types.AdvancedCombatSpecialAbility,
Types.AdvancedKarmaSpecialAbility,
Types.AdvancedMagicalSpecialAbility,
Types.AdvancedSkillSpecialAbility,
Types.Advantage,
Types.AlternativeRule,
Types.Ammunition,
Types.AncestorGlyph,
Types.AnimalCare,
Types.AnimalDisease,
Types.Animal,
Types.AnimalShapePath,
Types.AnimalShape,
Types.AnimalShapeSize,
Types.AnimalType,
Types.AnimistPower,
Types.Tribe,
Types.ArcaneBardTradition,
Types.ArcaneDancerTradition,
Types.ArcaneOrbEnchantment,
Types.Armor,
Types.ArmorType,
Types.Aspect,
Types.AttireEnchantment,
Types.Attribute,
Types.BandageOrRemedy,
Types.BlessedTradition,
Types.Blessing,
Types.Book,
Types.BowlEnchantment,
Types.BrawlingSpecialAbility,
Types.Brew,
Types.Cantrip,
Types.CauldronEnchantment,
Types.CeremonialItem,
Types.CeremonialItemSpecialAbility,
Types.Ceremony,
Types.ChronicleEnchantment,
Types.CloseCombatTechnique,
Types.Clothes,
Types.ClothingPackage,
Types.CombatSpecialAbility,
Types.CombatStyleSpecialAbility,
Types.CommandSpecialAbility,
Types.Condition,
Types.Container,
Types.Continent,
Types.CoreRule,
Types.Culture,
Types.Curse,
Types.DaggerRitual,
Types.DerivedCharacteristic,
Types.Disadvantage,
Types.Disease,
Types.DominationRitual,
Types.Element,
Types.Elixir,
Types.ElvenMagicalSong,
Types.EquipmentOfBlessedOnes,
Types.EquipmentPackage,
Types.ExperienceLevel,
Types.EyeColor,
Types.FamiliarSpecialAbility,
Types.FamiliarsTrick,
Types.FatePointSexSpecialAbility,
Types.FatePointSpecialAbility,
Types.FocusRule,
Types.Subject,
Types.FoolsHatEnchantment,
Types.GemOrPreciousStone,
Types.GeneralSpecialAbility,
Types.GeodeRitual,
Types.HairColor,
Types.IlluminationLightSource,
Types.IlluminationRefillOrSupply,
Types.InstrumentEnchantment,
Types.JesterTrick,
Types.Jewelry,
Types.KarmaSpecialAbility,
Types.Krallenkettenzauber,
Types.Language,
Types.Curriculum,
Types.Guideline,
Types.Laboratory,
Types.Liebesspielzeug,
Types.LiturgicalChant,
Types.LiturgicalStyleSpecialAbility,
Types.LuxuryGood,
Types.LycantropicGift,
Types.MagicalArtifact,
Types.MagicalDance,
Types.MagicalMelody,
Types.MagicalRune,
Types.MagicalSign,
Types.MagicalSpecialAbility,
Types.MagicalTradition,
Types.MagicStyleSpecialAbility,
Types.MetaCondition,
Types.MusicalInstrument,
Types.Newspaper,
Types.OptionalRule,
Types.OrbEnchantment,
Types.OrienteeringAid,
Types.PactCategory,
Types.PactGift,
Types.PatronCategory,
Types.Patron,
Types.PersonalityTrait,
Types.PlayerType,
Types.Poison,
Types.Profession,
Types.Property,
Types.ProtectiveWardingCircleSpecialAbility,
Types.Publication,
Types.Race,
Types.RangedCombatTechnique,
Types.Reach,
Types.Region,
Types.RingEnchantment,
Types.Ritual,
Types.RopeOrChain,
Types.Script,
Types.Sermon,
Types.Service,
Types.SexPractice,
Types.SexSpecialAbility,
Types.SickleRitual,
Types.SikaryanDrainSpecialAbility,
Types.SkillGroup,
Types.SkillModificationLevel,
Types.Skill,
Types.SkillStyleSpecialAbility,
Types.SocialStatus,
Types.Spell,
Types.SpellSwordEnchantment,
Types.StaffEnchantment,
Types.State,
Types.Stationery,
Types.Talisman,
Types.TargetCategory,
Types.ThievesTool,
Types.ToolOfTheTrade,
Types.ToyEnchantment,
Types.TradeSecret,
Types.TravelGearOrTool,
Types.Trinkhornzauber,
Types.VampiricGift,
Types.Vehicle,
Types.Vision,
Types.WandEnchantment,
Types.WeaponAccessory,
Types.WeaponEnchantment,
Types.Weapon,
Types.ZibiljaRitual,
],
Locale
Types.Locale,
)
// /**
-41
View File
@@ -1,41 +0,0 @@
import { PluralizationCategories } from "../types/_I18n.js"
import { UI } from "../types/UI.js"
const pluralType: {
[K in keyof UI as UI[K] extends PluralizationCategories ? K : never]: Intl.PluralRuleType
} = {
"{0} Adventure Points": "cardinal",
"You are missing {0} Adventure Points to do this.": "cardinal",
"since the {0}. printing": "ordinal",
"removed in {0}. printing": "ordinal",
"{0} actions": "cardinal",
"{0} hours": "cardinal",
"{0} minutes": "cardinal",
"{0} rounds": "cardinal",
"{0} seduction actions": "cardinal",
", {0} of which are permanent": "cardinal",
"{0} centuries": "cardinal",
"{0} combat rounds": "cardinal",
"{0} days": "cardinal",
"{0} months": "cardinal",
"{0} mos.": "cardinal",
"{0} seconds": "cardinal",
"{0} weeks": "cardinal",
"{0} wks.": "cardinal",
"{0} years": "cardinal",
"{0} yrs.": "cardinal",
"{0} miles": "cardinal",
"{0} yards": "cardinal",
}
/**
* The keys of the UI strings that support pluralization categories.
*/
export type KeySupportingPluralizationCategories = keyof typeof pluralType
/**
* Returns the plural rule type for the given key.
*/
export const getPluralRuleTypeForKey = (
key: KeySupportingPluralizationCategories
): Intl.PluralRuleType => pluralType[key]
+11 -19
View File
@@ -1,21 +1,11 @@
import { Boolean, Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import {
ap_value,
ap_value_append,
ap_value_l10n,
input,
levels,
maximum,
name,
name_in_library,
range_l10n,
rules,
select_options,
skill_applications,
skill_uses,
} from "./_Activatable.js"
import { input, levels, maximum, name, name_in_library, range_l10n, rules } from "./_Activatable.js"
import { ap_value, ap_value_append, ap_value_l10n } from "./_ActivatableAdventurePointsValue.js"
import { automatic_entries } from "./_ActivatableAutomatic.js"
import { explicit_select_options, select_options } from "./_ActivatableSelectOptions.js"
import { skill_applications, skill_uses } from "./_ActivatableSkillApplicationsAndUses.js"
import { AdvantageDisadvantagePrerequisites } from "./_Prerequisite.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -26,9 +16,11 @@ export const Advantage = Entity(import.meta.url, {
Object({
levels,
select_options,
explicit_select_options,
skill_applications,
skill_uses,
maximum,
automatic_entries,
prerequisites: Optional({
type: IncludeIdentifier(AdvantageDisadvantagePrerequisites),
}),
@@ -43,9 +35,9 @@ export const Advantage = Entity(import.meta.url, {
type: Boolean(),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AdvantageTranslation",
"Advantage",
Object({
name,
name_in_library,
@@ -57,7 +49,7 @@ export const Advantage = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+2 -2
View File
@@ -1,5 +1,5 @@
import { Array, Entity, IncludeIdentifier, Object, Required } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { cause, Cause, DiseaseTranslation, level, resistance } from "./_DiseasePoison.js"
import { AnimalTypeIdentifier } from "./_Identifier.js"
import { src } from "./source/_PublicationRef.js"
@@ -25,7 +25,7 @@ If no causes are given, the disease is not communicable to intelligent creatures
type: Array(IncludeIdentifier(Cause)),
}),
src,
translations: NestedLocaleMap(Required, "AnimalDiseaseTranslation", DiseaseTranslation),
translations: NestedTranslationMap(Required, "AnimalDisease", DiseaseTranslation),
}),
displayName: {},
})
+4 -4
View File
@@ -1,20 +1,20 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const AnimalType = Entity(import.meta.url, {
name: "AnimalType",
namePlural: "AnimalTypes",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AnimalTypeTranslation",
"AnimalType",
Object({
name: Required({
comment: "The animal types name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+5 -5
View File
@@ -1,14 +1,14 @@
import { Entity, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Aspect = Entity(import.meta.url, {
name: "Aspect",
namePlural: "Aspects",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AspectTranslation",
"Aspect",
Object({
name: Required({
comment: "The aspects name.",
@@ -16,12 +16,12 @@ export const Aspect = Entity(import.meta.url, {
}),
master_of_aspect_suffix: Optional({
comment:
"The aspects name appended to the simple name (not `name_in_library`) of the special ability *Master of (Aspect)*.",
"In some languages, the aspects grammatical gender influences the full name of a *Master of (Aspect)* instance. To make this possible, the *name* (**not** the *name in library*) is joined with what is contained in this field.",
type: String({
minLength: 1,
}),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Attribute = Entity(import.meta.url, {
name: "Attribute",
@@ -7,9 +7,9 @@ export const Attribute = Entity(import.meta.url, {
comment: "A base attribute of a character.",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AttributeTranslation",
"Attribute",
Object({
name: Required({
comment: "The attributes name.",
@@ -23,7 +23,7 @@ export const Attribute = Entity(import.meta.url, {
comment: "The description of the attribute.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+9 -8
View File
@@ -11,8 +11,9 @@ import {
TypeAlias,
} from "tsondb/schema/def"
import { DurationUnit } from "./_ActivatableSkillDuration.js"
import { FixedRange } from "./_ActivatableSkillRange.js"
import { AffectedTargetCategories } from "./_ActivatableSkillTargetCategory.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -30,9 +31,9 @@ export const Blessing = Entity(import.meta.url, {
type: IncludeIdentifier(AffectedTargetCategories),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"BlessingTranslation",
"Blessing",
Object({
name: Required({
comment: "The blessings name.",
@@ -57,7 +58,7 @@ export const Blessing = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -78,7 +79,7 @@ const BlessingRange = Enum(import.meta.url, {
values: () => ({
Self: EnumCase({ type: null }),
Touch: EnumCase({ type: null }),
Fixed: EnumCase({ type: null }),
Fixed: EnumCase({ type: IncludeIdentifier(FixedRange) }),
}),
})
@@ -110,15 +111,15 @@ const IndefiniteBlessingDuration = TypeAlias(import.meta.url, {
name: "IndefiniteBlessingDuration",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IndefiniteBlessingDurationTranslation",
"IndefiniteBlessingDuration",
Object({
description: Required({
comment: "A description of the duration.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
})
+11 -11
View File
@@ -22,7 +22,7 @@ import {
PropertyIdentifier,
} from "./_Identifier.js"
import { ResponsiveText } from "./_ResponsiveText.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -49,9 +49,9 @@ export const Cantrip = Entity(import.meta.url, {
type: IncludeIdentifier(CantripNote),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"CantripTranslation",
"Cantrip",
Object({
name: Required({
comment: "The cantrips name.",
@@ -76,7 +76,7 @@ export const Cantrip = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -130,15 +130,15 @@ const CommonCantripTraditionNote = TypeAlias(import.meta.url, {
comment: "The magical traditions identifier.",
type: MagicalTraditionIdentifier(),
}),
translations: NestedLocaleMap(
Required,
"CommonCantripTraditionNoteTranslation",
translations: NestedTranslationMap(
Optional,
"CommonCantripTraditionNote",
Object({
note: Required({
comment: "A note, appended to the generated string in parenthesis.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
})
@@ -195,15 +195,15 @@ const IndefiniteCantripDuration = TypeAlias(import.meta.url, {
name: "IndefiniteCantripDuration",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IndefiniteCantripDurationTranslation",
"IndefiniteCantripDuration",
Object({
description: Required({
comment: "A description of the duration.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
+4 -4
View File
@@ -16,7 +16,7 @@ import { Enhancement } from "./_Enhancements.js"
import { ImprovementCost } from "./_ImprovementCost.js"
import { LiturgyPrerequisites } from "./_Prerequisite.js"
import { SkillCheck, SkillCheckPenalty } from "./_SkillCheck.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -55,9 +55,9 @@ export const Ceremony = Entity(import.meta.url, {
type: IncludeIdentifier(LiturgyPrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"LiturgicalChantTranslation",
"Ceremony",
Object({
name: Required({
comment: "The ceremonys name.",
@@ -96,7 +96,7 @@ export const Ceremony = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
enhancements: Required({
type: ChildEntities(Enhancement),
+7 -7
View File
@@ -12,7 +12,7 @@ import {
} from "tsondb/schema/def"
import { AttributeIdentifier } from "./_Identifier.js"
import { ImprovementCost } from "./_ImprovementCost.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -39,9 +39,9 @@ export const CloseCombatTechnique = Entity(import.meta.url, {
type: IncludeIdentifier(ImprovementCost),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"CloseCombatTechniqueTranslation",
"CloseCombatTechnique",
Object({
name: Required({
comment: "The combat techniques name.",
@@ -54,7 +54,7 @@ export const CloseCombatTechnique = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -99,9 +99,9 @@ export const RangedCombatTechnique = Entity(import.meta.url, {
type: IncludeIdentifier(ImprovementCost),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"RangedCombatTechniqueTranslation",
"RangedCombatTechnique",
Object({
name: Required({
comment: "The combat techniques name.",
@@ -114,7 +114,7 @@ export const RangedCombatTechnique = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -7,7 +7,7 @@ import {
Required,
String,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -17,9 +17,9 @@ export const Condition = Entity(import.meta.url, {
type: () =>
Object({
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ConditionTranslation",
"Condition",
Object({
name: Required({
comment: "The conditions name.",
@@ -39,7 +39,7 @@ export const Condition = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Continent = Entity(import.meta.url, {
name: "Continent",
@@ -8,15 +8,15 @@ export const Continent = Entity(import.meta.url, {
"Continents are mostly referenced to in languages and scripts that occur on a specific continent.",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ContinentTranslation",
"Continent",
Object({
name: Required({
comment: "The continent name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+12 -12
View File
@@ -30,7 +30,7 @@ import {
SocialStatusIdentifier,
} from "./_Identifier.js"
import { BinarySex } from "./_Sex.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { MundaneProfessionGroup } from "./Profession.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -66,28 +66,28 @@ export const Culture = Entity(import.meta.url, {
comment: "A list of common advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
common_disadvantages: Optional({
comment: "A list of common disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
uncommon_advantages: Optional({
comment: "A list of uncommon advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
uncommon_disadvantages: Optional({
comment: "A list of uncommon disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
common_skills: Required({
@@ -103,9 +103,9 @@ export const Culture = Entity(import.meta.url, {
type: Array(IncludeIdentifier(CulturalPackageItem), { minItems: 1 }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"CultureTranslation",
"Culture",
Object({
name: Required({
comment: "The races name.",
@@ -138,7 +138,7 @@ export const Culture = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -333,7 +333,7 @@ const GroupedCommonProfessions = TypeAlias(import.meta.url, {
]),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
@@ -420,7 +420,7 @@ const CommonNames = TypeAlias(import.meta.url, {
type: String({ minLength: 1 }),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
@@ -442,7 +442,7 @@ const CommonNameGroup = TypeAlias(import.meta.url, {
type: Array(IncludeIdentifier(CommonName), { minItems: 1 }),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
@@ -459,6 +459,6 @@ const CommonName = TypeAlias(import.meta.url, {
type: String({ minLength: 1 }),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
+4 -4
View File
@@ -7,7 +7,7 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { DerivedCharacteristicPrerequisites } from "./_Prerequisite.js"
import { src } from "./source/_PublicationRef.js"
@@ -20,9 +20,9 @@ export const DerivedCharacteristic = Entity(import.meta.url, {
type: IncludeIdentifier(DerivedCharacteristicPrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"DerivedCharacteristicTranslation",
"DerivedCharacteristic",
Object({
name: Required({
comment: "The derived characteristics name.",
@@ -36,7 +36,7 @@ export const DerivedCharacteristic = Entity(import.meta.url, {
comment: "Possible calculation strings for the final value.",
type: IncludeIdentifier(CalculationTranslation),
}),
})
}),
),
}),
displayName: {},
+9 -18
View File
@@ -7,22 +7,12 @@ import {
Required,
String,
} from "tsondb/schema/def"
import {
ap_value,
ap_value_append,
ap_value_l10n,
input,
levels,
maximum,
name,
name_in_library,
rules,
select_options,
skill_applications,
skill_uses,
} from "./_Activatable.js"
import { input, levels, maximum, name, name_in_library, rules } from "./_Activatable.js"
import { ap_value, ap_value_append, ap_value_l10n } from "./_ActivatableAdventurePointsValue.js"
import { explicit_select_options, select_options } from "./_ActivatableSelectOptions.js"
import { skill_applications, skill_uses } from "./_ActivatableSkillApplicationsAndUses.js"
import { AdvantageDisadvantagePrerequisites } from "./_Prerequisite.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -33,6 +23,7 @@ export const Disadvantage = Entity(import.meta.url, {
Object({
levels,
select_options,
explicit_select_options,
skill_applications,
skill_uses,
maximum,
@@ -50,9 +41,9 @@ export const Disadvantage = Entity(import.meta.url, {
type: Boolean(),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"DisadvantageTranslation",
"Disadvantage",
Object({
name,
name_in_library,
@@ -67,7 +58,7 @@ export const Disadvantage = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+8 -3
View File
@@ -1,6 +1,7 @@
import { Entity, Object, Required } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { NestedTranslationMap } from "./Locale.js"
import { cause, DiseaseTranslation, level, resistance } from "./_DiseasePoison.js"
import { RecipeTradeSecret } from "./equipment/item/_Herbary.js"
import { src } from "./source/_PublicationRef.js"
export const Disease = Entity(import.meta.url, {
@@ -11,8 +12,12 @@ export const Disease = Entity(import.meta.url, {
level,
resistance,
cause,
trade_secret: Optional({
comment: "AP value and prerequisites of the diseases trade secret.",
type: IncludeIdentifier(RecipeTradeSecret),
}),
src,
translations: NestedLocaleMap(Required, "DiseaseTranslation", DiseaseTranslation),
translations: NestedTranslationMap(Required, "Disease", DiseaseTranslation),
}),
displayName: {},
})
+4 -4
View File
@@ -1,20 +1,20 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Element = Entity(import.meta.url, {
name: "Element",
namePlural: "Elements",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ElementTranslation",
"Element",
Object({
name: Required({
comment: "The elements name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, Integer, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const ExperienceLevel = Entity(import.meta.url, {
name: "ExperienceLevel",
@@ -35,15 +35,15 @@ export const ExperienceLevel = Entity(import.meta.url, {
comment: "The maximum of spells of an unfamiliar tradition you can activate.",
type: Integer(),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ExperienceLevelTranslation",
"ExperienceLevel",
Object({
name: Required({
comment: "The experience levels name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,20 +1,20 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const EyeColor = Entity(import.meta.url, {
name: "EyeColor",
namePlural: "EyeColors",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"EyeColorTranslation",
"EyeColor",
Object({
name: Required({
comment: "The eye colors name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+11 -11
View File
@@ -20,7 +20,7 @@ import {
} from "./_ActivatableSkillDuration.js"
import { AnimalTypeIdentifier, PropertyIdentifier } from "./_Identifier.js"
import { ResponsiveText, ResponsiveTextOptional } from "./_ResponsiveText.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -49,9 +49,9 @@ If no animal types are given, the animal disease applies to all animal types.`,
type: Integer({ minimum: 1 }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"FamiliarsTrickTranslation",
"FamiliarsTrick",
Object({
name: Required({
comment: "The familiars tricks name.",
@@ -72,7 +72,7 @@ If no animal types are given, the animal disease applies to all animal types.`,
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -90,15 +90,15 @@ const IndefiniteFamiliarsTrickProperty = TypeAlias(import.meta.url, {
name: "IndefiniteFamiliarsTrickProperty",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"IndefiniteFamiliarsTrickPropertyTranslation",
"IndefiniteFamiliarsTrickProperty",
Object({
description: Required({
comment: "A description of the property.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
@@ -153,9 +153,9 @@ const FamiliarsTrickFixedOneTimeCost = TypeAlias(import.meta.url, {
comment: "The interval in which you have to pay the AE cost again.",
type: IncludeIdentifier(DurationUnitValue),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"FamiliarsTrickFixedOneTimeCostTranslation",
"FamiliarsTrickFixedOneTimeCost",
Object(
{
per: Optional({
@@ -164,8 +164,8 @@ const FamiliarsTrickFixedOneTimeCost = TypeAlias(import.meta.url, {
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
+4 -4
View File
@@ -1,20 +1,20 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const HairColor = Entity(import.meta.url, {
name: "HairColor",
namePlural: "HairColors",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"HairColorTranslation",
"HairColor",
Object({
name: Required({
comment: "The hair colors name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+11 -11
View File
@@ -12,7 +12,7 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import {
CurriculumIdentifier,
ElementIdentifier,
@@ -34,15 +34,15 @@ export const Guideline = Entity(import.meta.url, {
comment: "Maximum number of spells that can be exchanged.",
type: Integer({ minimum: 0 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"GuidelineTranslation",
"Guideline",
Object({
name: Required({
comment: "The guidelines name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
@@ -72,9 +72,9 @@ export const Curriculum = Entity(import.meta.url, {
type: ChildEntities(LessonPackage),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"CurriculumTranslation",
"Curriculum",
Object({
name: Required({
comment: "The institutions name.",
@@ -83,7 +83,7 @@ export const Curriculum = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -187,9 +187,9 @@ export const LessonPackage = Entity(import.meta.url, {
skills: Optional({
type: Array(IncludeIdentifier(AbilityAdjustment), { minItems: 1, uniqueItems: true }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"LessonPackageTranslation",
"LessonPackage",
Object({
name: Required({
comment: "The lesson packages name.",
@@ -200,10 +200,10 @@ export const LessonPackage = Entity(import.meta.url, {
"The spell values difference of the lesson package. Use this field to specify a text that is displayed instead of the generated `spellwork_changes` list. The field is displayed even if no list is present.",
type: String({ minLength: 1 }),
}),
})
}),
),
},
{ minProperties: 3 }
{ minProperties: 3 },
),
parentReferenceKey: "curriculum",
displayName: {},
+4 -4
View File
@@ -16,7 +16,7 @@ import { Enhancement } from "./_Enhancements.js"
import { ImprovementCost } from "./_ImprovementCost.js"
import { LiturgyPrerequisites } from "./_Prerequisite.js"
import { SkillCheck, SkillCheckPenalty } from "./_SkillCheck.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -55,9 +55,9 @@ export const LiturgicalChant = Entity(import.meta.url, {
type: IncludeIdentifier(LiturgyPrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"LiturgicalChantTranslation",
"LiturgicalChant",
Object({
name: Required({
comment: "The liturgical chants name.",
@@ -96,7 +96,7 @@ export const LiturgicalChant = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
enhancements: Required({
type: ChildEntities(Enhancement),
+1340 -1298
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -7,7 +7,7 @@ import {
Required,
String,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -19,9 +19,9 @@ export const MetaCondition = Entity(import.meta.url, {
type: () =>
Object({
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"MetaConditionTranslation",
"MetaCondition",
Object({
name: Required({
comment: "The meta conditions name.",
@@ -41,7 +41,7 @@ export const MetaCondition = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+10 -10
View File
@@ -8,7 +8,7 @@ import {
String,
} from "tsondb/schema/def"
import { PactCategoryIdentifier } from "./_Identifier.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -26,9 +26,9 @@ export const PactCategory = Entity(import.meta.url, {
type: ChildEntities(PactDomain),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PactCategoryTranslation",
"PactCategory",
Object({
name: Required({
comment: "The pact categorys name.",
@@ -37,7 +37,7 @@ export const PactCategory = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -52,15 +52,15 @@ export const PactType = Entity(import.meta.url, {
comment: "The pact category this type belongs to.",
type: PactCategoryIdentifier(),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PactTypeTranslation",
"PactType",
Object({
name: Required({
comment: "The types name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
parentReferenceKey: "parent",
@@ -76,15 +76,15 @@ export const PactDomain = Entity(import.meta.url, {
comment: "The pact category this domain belongs to.",
type: PactCategoryIdentifier(),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PactDomainTranslation",
"PactDomain",
Object({
name: Required({
comment: "The domains name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
parentReferenceKey: "parent",
+6 -8
View File
@@ -14,14 +14,14 @@ import {
import {
AdvantageIdentifier,
AttributeIdentifier,
CultureIdentifier,
DisadvantageIdentifier,
PatronCategoryIdentifier,
SkillIdentifier,
TribeIdentifier,
} from "./_Identifier.js"
import { SpellworkIdentifier } from "./_IdentifierGroup.js"
import { ImprovementCost } from "./_ImprovementCost.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { src } from "./source/_PublicationRef.js"
export const Patron = Entity(import.meta.url, {
@@ -75,15 +75,15 @@ export const Patron = Entity(import.meta.url, {
type: Array(IncludeIdentifier(SpellworkIdentifier), { minItems: 1, uniqueItems: true }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PatronTranslation",
"Patron",
Object({
name: Required({
comment: "The patrons name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
@@ -96,11 +96,9 @@ const PatronCulture = TypeAlias(import.meta.url, {
type: () =>
Object({
set: Required({
comment: "The AE cost value.",
type: Array(CultureIdentifier(), { uniqueItems: true }),
type: Array(TribeIdentifier(), { uniqueItems: true }),
}),
operation: Optional({
comment: "The interval in which you have to pay the AE cost again.",
type: IncludeIdentifier(PatronCultureOperation),
}),
}),
+4 -4
View File
@@ -1,5 +1,5 @@
import { Array, Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { CultureIdentifier } from "./_Identifier.js"
export const PatronCategory = Entity(import.meta.url, {
@@ -12,15 +12,15 @@ export const PatronCategory = Entity(import.meta.url, {
"The list of cultures where patrons from this category can be the primary patron of.",
type: Array(CultureIdentifier(), { minItems: 1, uniqueItems: true }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PatronCategoryTranslation",
"PatronCategory",
Object({
name: Required({
comment: "The patron categorys name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -9,7 +9,7 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { PersonalityTraitIdentifier } from "./_Identifier.js"
import { PersonalityTraitPrerequisites } from "./_Prerequisite.js"
import { Errata } from "./source/_Erratum.js"
@@ -34,9 +34,9 @@ export const PersonalityTrait = Entity(import.meta.url, {
type: Array(PersonalityTraitIdentifier(), { minItems: 1, uniqueItems: true }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PersonalityTraitTranslation",
"PersonalityTrait",
Object({
name: Required({
comment: "The personality traits name.",
@@ -50,7 +50,7 @@ export const PersonalityTrait = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+29 -31
View File
@@ -41,7 +41,7 @@ import {
SpellworkIdentifier,
} from "./_IdentifierGroup.js"
import { ProfessionPrerequisites } from "./_Prerequisite.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -70,7 +70,7 @@ export const Profession = Entity(import.meta.url, {
const [firstProfessionVersion, ...otherProfessionVersions] = getChildInstancesForInstanceId(
Profession.name,
instanceId,
ProfessionVersion.name
ProfessionVersion.name,
)
if (!firstProfessionVersion) {
@@ -87,7 +87,7 @@ export const Profession = Entity(import.meta.url, {
return {
...displayName,
name: `${displayName.name} (+${otherProfessionVersions.length} version${
name: `${displayName.name} (+${otherProfessionVersions.length.toString()} version${
otherProfessionVersions.length > 1 ? "s" : ""
})`,
}
@@ -132,11 +132,6 @@ export const ProfessionVersion = Entity(import.meta.url, {
comment: "The associated profession.",
type: ProfessionIdentifier(),
}),
prerequisites: Optional({
comment:
"Which prerequisites must be met to buy the stat block? For example, a character might need the advantage Spellcaster or Blessed. Note: the AP cost for a profession package does not include these prerequisites.",
type: IncludeIdentifier(ProfessionPrerequisites),
}),
package: Required({
comment:
"A list of available race variants where one has to be selected. If no variants are to be selected, a single variant with no name has to be provided which will be used as the missing values for the base race.",
@@ -146,14 +141,14 @@ export const ProfessionVersion = Entity(import.meta.url, {
comment: "A list of typical advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
suggested_disadvantages: Optional({
comment: "A list of typical disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
unsuitable_advantages: Optional({
@@ -161,7 +156,7 @@ export const ProfessionVersion = Entity(import.meta.url, {
"A list of advantages that do not fit well with this profession; to be checked with the GM before taking any of them.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
unsuitable_disadvantages: Optional({
@@ -169,13 +164,13 @@ export const ProfessionVersion = Entity(import.meta.url, {
"A list of disadvantages that do not fit well with this profession; to be checked with the GM before taking any of them.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ProfessionVersionTranslation",
"ProfessionVersion",
ObjectType({
name: Required({
comment: "The basic professions name.",
@@ -207,7 +202,7 @@ export const ProfessionVersion = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
parentReferenceKey: "profession",
@@ -216,17 +211,15 @@ export const ProfessionVersion = Entity(import.meta.url, {
pathInLocaleMap: "name.default",
},
displayNameCustomizer: ({ instance, instanceId, locales }) => {
if (typeof instance.translations === "object" && instance.translations !== null) {
const translations = Object.entries(instance.translations)
for (const locale of locales) {
const translation = translations.find(([key]) => key === locale)
if (translation) {
const [, value] = translation
return {
name:
value.name.default + (value.specification ? ` (${value.specification.default})` : ""),
localeId: locale,
}
const translations = Object.entries(instance.translations)
for (const locale of locales) {
const translation = translations.find(([key]) => key === locale)
if (translation) {
const [, value] = translation
return {
name:
value.name.default + (value.specification ? ` (${value.specification.default})` : ""),
localeId: locale,
}
}
}
@@ -255,6 +248,11 @@ export const ProfessionPackage = Entity(import.meta.url, {
comment: "What does the professional package cost in adventure points?",
type: Integer({ minimum: 0 }),
}),
prerequisites: Optional({
comment:
"Which prerequisites must be met to buy the stat block? For example, a character might need the advantage Spellcaster or Blessed. Note: the AP cost for a profession package does not include these prerequisites.",
type: IncludeIdentifier(ProfessionPrerequisites),
}),
options: Optional({
comment:
"In some areas, the profession package grants a loose set of stats where the player must choose between different options for the profession package.",
@@ -361,9 +359,9 @@ export const ProfessionVariant = Entity(import.meta.url, {
"Clerical profession variants receive these liturgical chants at the listed skill ratings. These liturgical chants are considered activated.",
type: Array(IncludeIdentifier(LiturgicalChantRating), { minItems: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ProfessionVariantTranslation",
"ProfessionVariant",
ObjectType({
name: Required({
comment: "The profession variants name.",
@@ -377,7 +375,7 @@ export const ProfessionVariant = Entity(import.meta.url, {
comment: `A text that is appended to the generated text for the profession variant.`,
type: String({ minLength: 1 }),
}),
})
}),
),
}),
parentReferenceKey: "profession_package",
@@ -581,7 +579,7 @@ const ProfessionPackageOptions = TypeAlias(import.meta.url, {
type: IncludeIdentifier(SkillsOptions),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
@@ -622,7 +620,7 @@ const ProfessionVariantPackageOptions = TypeAlias(import.meta.url, {
type: GenIncludeIdentifier(VariantOptionAction, [IncludeIdentifier(SkillsOptions)]),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
+4 -4
View File
@@ -1,6 +1,6 @@
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { SkillCheck } from "./_SkillCheck.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Property = Entity(import.meta.url, {
name: "Property",
@@ -12,15 +12,15 @@ export const Property = Entity(import.meta.url, {
"The property checks attributes. Only the properties from the Core Rules have defined property checks.",
type: IncludeIdentifier(SkillCheck),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PropertyTranslation",
"Property",
Object({
name: Required({
comment: "The propertys name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+16 -14
View File
@@ -26,7 +26,7 @@ import {
RaceIdentifier,
} from "./_Identifier.js"
import { ExperienceLevel } from "./ExperienceLevel.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -57,14 +57,14 @@ export const Race = Entity(import.meta.url, {
comment: "A list of strongly recommended advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
strongly_recommended_disadvantages: Optional({
comment: "A list of strongly recommended disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
weight: Required({
@@ -76,6 +76,7 @@ export const Race = Entity(import.meta.url, {
"Defines the starting ages for the race. It depends on the selected experience level.",
type: NestedEntityMap({
name: "StartingAgeConfigForExperienceLevel",
namePlural: "StartingAgeConfigsForExperienceLevel",
secondaryEntity: ExperienceLevel,
type: Object({
base: Required({
@@ -88,6 +89,7 @@ export const Race = Entity(import.meta.url, {
type: IncludeIdentifier(Dice),
}),
}),
displayName: null,
}),
}),
variants: Required({
@@ -96,9 +98,9 @@ export const Race = Entity(import.meta.url, {
type: ChildEntities(RaceVariant),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"RaceTranslation",
"Race",
Object({
name: Required({
comment: "The races name.",
@@ -123,7 +125,7 @@ export const Race = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -172,7 +174,7 @@ The array only permits a single entry because no race specified more than one se
}),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
@@ -289,28 +291,28 @@ export const RaceVariant = Entity(import.meta.url, {
comment: "A list of common advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
common_disadvantages: Optional({
comment: "A list of common disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
uncommon_advantages: Optional({
comment: "A list of uncommon advantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [AdvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
uncommon_disadvantages: Optional({
comment: "A list of uncommon disadvantages.",
type: Array(
GenIncludeIdentifier(CommonnessRatedAdvantageDisadvantage, [DisadvantageIdentifier()]),
{ minItems: 1 }
{ minItems: 1 },
),
}),
hair_color: Optional({
@@ -327,9 +329,9 @@ export const RaceVariant = Entity(import.meta.url, {
comment: "Configuration for random height generation.",
type: IncludeIdentifier(RandomHeightGeneration),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"RaceVariantTranslation",
"RaceVariant",
Object({
name: Required({
comment:
@@ -352,7 +354,7 @@ export const RaceVariant = Entity(import.meta.url, {
comment: "The respective uncommon disadvantages text from the source book.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
parentReferenceKey: "race",
+4 -4
View File
@@ -1,20 +1,20 @@
import { Entity, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Region = Entity(import.meta.url, {
name: "Region",
namePlural: "Regions",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"RegionTranslation",
"Region",
Object({
name: Required({
comment: "The regions name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -16,7 +16,7 @@ import { ImprovementCost } from "./_ImprovementCost.js"
import { SpellworkPrerequisites } from "./_Prerequisite.js"
import { SkillCheck, SkillCheckPenalty } from "./_SkillCheck.js"
import { SpellworkTraditions } from "./_Spellwork.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -59,9 +59,9 @@ export const Ritual = Entity(import.meta.url, {
type: IncludeIdentifier(SpellworkPrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"RitualTranslation",
"Ritual",
Object({
name: Required({
comment: "The rituals name.",
@@ -95,7 +95,7 @@ export const Ritual = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
enhancements: Required({
type: ChildEntities(Enhancement),
+4 -4
View File
@@ -9,7 +9,7 @@ import {
Required,
String,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -26,9 +26,9 @@ export const Service = Entity(import.meta.url, {
}),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ServiceTranslation",
"Service",
Object({
name: Required({
comment: "The services name.",
@@ -41,7 +41,7 @@ export const Service = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { src } from "./source/_PublicationRef.js"
export const SexPractice = Entity(import.meta.url, {
@@ -8,9 +8,9 @@ export const SexPractice = Entity(import.meta.url, {
type: () =>
Object({
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"StateTranslation",
"SexPractice",
Object({
name: Required({
comment: "The sex practices name.",
@@ -33,7 +33,7 @@ export const SexPractice = Entity(import.meta.url, {
comment: "Effects of a failed *Seduction* check.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+7 -7
View File
@@ -17,7 +17,7 @@ import {
import { SkillGroupIdentifier, SkillIdentifier } from "./_Identifier.js"
import { ImprovementCost } from "./_ImprovementCost.js"
import { SkillCheck } from "./_SkillCheck.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -48,9 +48,9 @@ export const Skill = Entity(import.meta.url, {
type: SkillGroupIdentifier(),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"SkillTranslation",
"Skill",
Object({
name: Required({
comment: "The skills name.",
@@ -89,7 +89,7 @@ export const Skill = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -130,15 +130,15 @@ export const SkillApplication = Entity(import.meta.url, {
comment: "The skill this application belongs to.",
type: SkillIdentifier(),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"SkillApplicationTranslation",
"SkillApplication",
Object({
name: Required({
comment: "The skill applications name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
parentReferenceKey: "parent",
+4 -4
View File
@@ -1,6 +1,6 @@
import { Entity, IncludeIdentifier, Object, Required, String } from "tsondb/schema/def"
import { SkillCheck } from "./_SkillCheck.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const SkillGroup = Entity(import.meta.url, {
name: "SkillGroup",
@@ -11,9 +11,9 @@ export const SkillGroup = Entity(import.meta.url, {
comment: "The skill groups skill check attributes",
type: IncludeIdentifier(SkillCheck),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"SkillGroupTranslation",
"SkillGroup",
Object({
name: Required({
comment: "The skill groups name.",
@@ -23,7 +23,7 @@ export const SkillGroup = Entity(import.meta.url, {
comment: "The skill groups long name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+10 -12
View File
@@ -14,7 +14,7 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const SkillModificationLevel = Entity(import.meta.url, {
name: "SkillModificationLevel",
@@ -29,9 +29,9 @@ export const SkillModificationLevel = Entity(import.meta.url, {
comment: "Configuration for this level for slow skills (rituals, ceremonies).",
type: IncludeIdentifier(SlowSkillModificationLevelConfig),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"SkillModificationLevelTranslation",
"SkillModificationLevel",
Object(
{
fast: Optional({
@@ -45,19 +45,17 @@ export const SkillModificationLevel = Entity(import.meta.url, {
type: IncludeIdentifier(LevelTypeConfigTranslation),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
displayName: null,
displayNameCustomizer: ({ instance, locales }) => ({
name: `${(instance.fast as any).casting_time} Act. / ${
(instance.slow as any).casting_time.value
} ${(instance.slow as any).casting_time.unit.kind} (fast/slow) — ${
(instance.fast as any).range === (instance.slow as any).range
? (instance.fast as any).range
: (instance.fast as any).range + "/" + (instance.slow as any).range + " (fast/slow)"
} m — ${(instance.fast as any).cost}/${(instance.slow as any).cost} (fast/slow) AE or KP`,
name: `${instance.fast.casting_time.toString()} Act. / ${instance.slow.casting_time.value.toString()} ${instance.slow.casting_time.unit.kind} (fast/slow) — ${
instance.fast.range === instance.slow.range
? instance.fast.range.toString()
: `${instance.fast.range.toString()}/${instance.slow.range.toString()} (fast/slow)`
} m — ${instance.fast.cost.toString()}/${instance.slow.cost.toString()} (fast/slow) AE or KP`,
localeId: locales[0],
}),
})
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, Integer, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const SocialStatus = Entity(import.meta.url, {
name: "SocialStatus",
@@ -11,15 +11,15 @@ export const SocialStatus = Entity(import.meta.url, {
"The social status order. The higher the order, the more powerful the social status. This has to be a unique value.",
type: Integer({ minimum: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"SocialStatusTranslation",
"SocialStatus",
Object({
name: Required({
comment: "The social status name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -16,7 +16,7 @@ import { ImprovementCost } from "./_ImprovementCost.js"
import { SpellworkPrerequisites } from "./_Prerequisite.js"
import { SkillCheck, SkillCheckPenalty } from "./_SkillCheck.js"
import { SpellworkTraditions } from "./_Spellwork.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -59,9 +59,9 @@ export const Spell = Entity(import.meta.url, {
type: IncludeIdentifier(SpellworkPrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"SpellTranslation",
"Spell",
Object({
name: Required({
comment: "The spells name.",
@@ -95,7 +95,7 @@ export const Spell = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
enhancements: Required({
type: ChildEntities(Enhancement),
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -9,9 +9,9 @@ export const State = Entity(import.meta.url, {
type: () =>
Object({
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"StateTranslation",
"State",
Object({
name: Required({
comment: "The states name.",
@@ -24,7 +24,7 @@ export const State = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -12,7 +12,7 @@ import {
TypeAlias,
} from "tsondb/schema/def"
import { BlessedTraditionIdentifier } from "./_Identifier.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -34,9 +34,9 @@ export const Talisman = Entity(import.meta.url, {
type: Integer({ minimum: 5, multipleOf: 5 }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"TalismanTranslation",
"Talisman",
Object({
name: Required({
comment: "The talismans name.",
@@ -53,7 +53,7 @@ export const Talisman = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, ObjectType, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { TargetCategoryIdentifier } from "./_Identifier.js"
export const TargetCategory = Entity(import.meta.url, {
@@ -11,15 +11,15 @@ export const TargetCategory = Entity(import.meta.url, {
comment: "A superordinate target category, if present.",
type: TargetCategoryIdentifier(),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"TargetCategoryTranslation",
"TargetCategory",
ObjectType({
name: Required({
comment: "The target categorys name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+1 -1709
View File
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
import {
Array,
Boolean,
EnumCase,
GenEnum,
GenIncludeIdentifier,
GenTypeAlias,
IncludeIdentifier,
Object,
Optional,
Param,
Required,
type Type,
TypeArgument,
} from "tsondb/schema/def"
import { MagicalTraditionIdentifier, PatronIdentifier } from "./_Identifier.js"
import { AdvancedSpecialAbilityRestrictedOptionIdentifier } from "./_IdentifierGroup.js"
import { DisplayOption } from "./prerequisites/DisplayOption.js"
const AdvancedSpecialAbility = GenEnum(import.meta.url, {
name: "AdvancedSpecialAbility",
comment: "A reference to an advanced special ability.",
parameters: [Param("Identifier")],
values: Identifier => ({
General: EnumCase({ type: TypeArgument(Identifier) }),
RestrictOptions: EnumCase({
type: GenIncludeIdentifier(RestrictAdvancedSpecialAbilityOptions, [TypeArgument(Identifier)]),
}),
OneOf: EnumCase({
type: GenIncludeIdentifier(OneOfAdvancedSpecialAbilityOptions, [TypeArgument(Identifier)]),
}),
DeriveFromExternalOption: EnumCase({
type: GenIncludeIdentifier(AdvancedSpecialAbilityDerivedFromExternalOption, [
TypeArgument(Identifier),
]),
}),
}),
})
const RestrictAdvancedSpecialAbilityOptions = GenTypeAlias(import.meta.url, {
name: "RestrictAdvancedSpecialAbilityOptions",
parameters: [Param("Identifier")],
type: Identifier =>
Object({
id: Required({
comment: "The advanced special abilitys identifier.",
type: TypeArgument(Identifier),
}),
option: Required({
comment:
"Specify the select option(s) that only are allowed for the referenced advanced special ability; others are disallowed.",
type: Array(IncludeIdentifier(AdvancedSpecialAbilityRestrictedOptionIdentifier), {
minItems: 1,
}),
}),
}),
})
const OneOfAdvancedSpecialAbilityOptions = GenTypeAlias(import.meta.url, {
name: "OneOfAdvancedSpecialAbilityOptions",
parameters: [Param("Identifier")],
type: Identifier =>
Object({
options: Required({
comment: "The possible advanced special abilities.",
type: Array(TypeArgument(Identifier), { minItems: 2 }),
}),
is_selection_required_on_purchase: Required({
comment:
"Do have to choose the advanced special ability when buying the style special ability? Otherwise the decision can be made later.",
type: Boolean(),
}),
display_option: Optional({
type: IncludeIdentifier(DisplayOption),
}),
}),
})
const AdvancedSpecialAbilityDerivedFromExternalOption = GenTypeAlias(import.meta.url, {
name: "AdvancedSpecialAbilityDerivedFromExternalOption",
parameters: [Param("Identifier")],
type: Identifier =>
Object({
external_entry: Required({
comment: "The possible advanced special abilities.",
type: MagicalTraditionIdentifier(),
}),
map: Required({
comment: "Map options from the external entry to allowed advanced special abilities.",
type: Array(
GenIncludeIdentifier(AdvancedSpecialAbilityDerivedFromExternalOptionMapping, [
TypeArgument(Identifier),
]),
{ minItems: 1 },
),
}),
display_option: Optional({
type: IncludeIdentifier(DisplayOption),
}),
}),
})
const AdvancedSpecialAbilityDerivedFromExternalOptionMapping = GenTypeAlias(import.meta.url, {
name: "AdvancedSpecialAbilityDerivedFromExternalOptionMapping",
parameters: [Param("Identifier")],
comment: "Mapping from an option of the external entry to an allowed advanced special ability.",
type: Identifier =>
Object({
from_option: Required({
comment: "The select options identifier.",
type: PatronIdentifier(),
}),
to_advanced: Required({
comment: "The advanced special abilitys identifier.",
type: TypeArgument(Identifier),
}),
}),
})
export const advanced = (identifier: Type) =>
Required({
comment:
"The Advanced Special Abilities for the respective Style Special Ability. Sometimes, only a specific select option or a set of select options of an entry is allowed, which can be modelled by the option property. It can also be that you can choose from a set of special abilities, but then you cant specify an option.",
type: Array(GenIncludeIdentifier(AdvancedSpecialAbility, [identifier]), {
minItems: 3,
maxItems: 3,
}),
})
@@ -0,0 +1,186 @@
import {
ArrayType as Array,
EnumDecl as Enum,
EnumCaseDecl as EnumCase,
GenIncludeIdentifierType as GenIncludeIdentifier,
IncludeIdentifier,
IntegerType as Integer,
ObjectType as Object,
Optional,
Required,
String,
TypeAlias,
} from "tsondb/schema/def"
import { ActivatableIdentifier } from "./_IdentifierGroup.js"
import { MathOperation } from "./_MathExpression.js"
import { BySizeCategory } from "./_SizeCategory.js"
import { NestedTranslationMap } from "./Locale.js"
const AdventurePointsValue = Enum(import.meta.url, {
name: "AdventurePointsValue",
values: () => ({
Fixed: EnumCase({ type: IncludeIdentifier(FixedAdventurePointsValue) }),
ByLevel: EnumCase({ type: IncludeIdentifier(AdventurePointsValueByLevel) }),
DerivedFromSelection: EnumCase({
type: IncludeIdentifier(AdventurePointsDerivedFromSelection),
}),
DependingOnActive: EnumCase({
type: IncludeIdentifier(AdventurePointsDependingOnActive),
}),
DependingOnActiveInstances: EnumCase({
type: IncludeIdentifier(AdventurePointsDependingOnActiveInstances),
}),
Indefinite: EnumCase({ type: null }),
}),
})
export const ap_value = Required({
comment: "The adventure points value.",
type: IncludeIdentifier(AdventurePointsValue),
})
export const ap_value_l10n = Optional({
comment: "The adventure points value.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
export const ap_value_append = Optional({
comment: "The string to append to the generated AP value text.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
export const FixedAdventurePointsValue = TypeAlias(import.meta.url, {
name: "FixedAdventurePointsValue",
comment:
"A fixed adventure points value. If the entry has levels, this is the cost per level as well.",
type: () => IncludeIdentifier(AdventurePointsSingleValue),
})
const AdventurePointsValueByLevel = TypeAlias(import.meta.url, {
name: "AdventurePointsValueByLevel",
comment: "An entry with levels may have different costs for each level.",
type: () =>
Object({
list: Required({
comment:
"The list of adventure point values, where the position in the list corresponds to the level (i.e. the first value is for level I, the second value is for level II, and so on). The length of the list must match the amount of levels the special ability has.",
type: Array(IncludeIdentifier(AdventurePointsSingleValue), { minItems: 2 }),
}),
additionalBySizeCategory: Optional({
comment:
"Values may be added to each level based on the size category of the character. Negative values mean that the AP value is lowered.",
type: GenIncludeIdentifier(BySizeCategory, [Integer()]),
}),
}),
})
const AdventurePointsDerivedFromSelection = TypeAlias(import.meta.url, {
name: "AdventurePointsDerivedFromSelection",
comment:
"The adventure points value is derived from the selection of the special ability. Its display value may be able to be derived from the given information for the select options. If that is not the case or the generated text would not match the original one, a replacement text can be provided.",
type: () =>
Object({
translations: NestedTranslationMap(
Optional,
"AdventurePointsDerivedFromSelection",
Object(
{
replacement: Optional({
comment:
"A replacement for the generated text if it would not match the original one.",
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 },
),
),
}),
})
const AdventurePointsSingleValue = TypeAlias(import.meta.url, {
name: "AdventurePointsSingleValue",
comment: "A single adventure points value.",
type: () => Integer({ minimum: 0 }),
})
export const AdventurePointsDependingOnActive = TypeAlias(import.meta.url, {
name: "AdventurePointsDependingOnActive",
comment: "The adventure points cost depends on if an instance of an entry is active.",
type: () =>
Object({
id: Required({
comment: "The identifier of the entry to that decides which AP value to pick.",
type: IncludeIdentifier(ActivatableIdentifier),
}),
active: Required({
comment: "The adventure points value if an instance of the referenced entry is active.",
type: IncludeIdentifier(AdventurePointsSingleValue),
}),
inactive: Required({
comment: "The adventure points value if no instance of the referenced entry is active.",
type: IncludeIdentifier(AdventurePointsSingleValue),
}),
}),
})
export const AdventurePointsDependingOnActiveInstances = Enum(import.meta.url, {
name: "AdventurePointsDependingOnActiveInstances",
comment:
"The adventure points cost depends on how many instances of the entry are already active.",
values: () => ({
Threshold: EnumCase({
type: IncludeIdentifier(AdventurePointsDependingOnActiveInstancesThreshold),
}),
Expression: EnumCase({
type: IncludeIdentifier(AdventurePointsDependingOnActiveInstancesExpression),
}),
}),
})
const AdventurePointsDependingOnActiveInstancesThreshold = TypeAlias(import.meta.url, {
name: "AdventurePointsDependingOnActiveInstancesThreshold",
comment:
"The adventure points cost depends on how many instances of the entry are already active.",
type: () =>
Object({
normal: Required({
comment: "The normal adventure points value.",
type: Integer({ minimum: 0 }),
}),
threshold: Required({
comment: "The number of active instances **after** which the alternative cost applies.",
type: Integer({ minimum: 1 }),
}),
alternative: Required({
comment: "The alternative adventure points value.",
type: Integer({ minimum: 0 }),
}),
}),
})
const AdventurePointsDependingOnActiveInstancesExpression = TypeAlias(import.meta.url, {
name: "AdventurePointsDependingOnActiveInstancesExpression",
comment:
"The adventure points cost depends on how many instances of the entry are already active.",
type: () =>
GenIncludeIdentifier(MathOperation, [
IncludeIdentifier(AdventurePointsDependingOnActiveInstancesExpressionValue),
]),
})
const AdventurePointsDependingOnActiveInstancesExpressionValue = Enum(import.meta.url, {
name: "AdventurePointsDependingOnActiveInstancesExpressionValue",
comment: `The type of value allowed in the expression to calculate the AP value.`,
values: () => ({
Constant: EnumCase({
comment: "A constant integer value.",
type: Integer(),
}),
Active: EnumCase({
comment: "The number of active instances.",
type: null,
}),
}),
})
+89
View File
@@ -0,0 +1,89 @@
import {
Array,
Boolean,
Enum,
EnumCase,
IncludeIdentifier,
Integer,
Object,
Optional,
Required,
TypeAlias,
} from "tsondb/schema/def"
import { ActivatableIdentifier } from "./_IdentifierGroup.js"
const AutomaticEntry = TypeAlias(import.meta.url, {
name: "AutomaticEntry",
type: () =>
Object({
action: Required({
comment: "What type of action is applied to the target entry?",
type: IncludeIdentifier(AutomaticEntryAction),
}),
apply_ap_value: Required({
comment:
"If an entry is added or removed, does is cost or grant adventure points or is it free of charge?",
type: Boolean(),
}),
target: Required({
comment:
"The entry that is to be added or removed. It can be a fixed entry or a selection where the player must choose one entry.",
type: IncludeIdentifier(AutomaticEntryTarget),
}),
}),
})
export const automatic_entries = Optional({
comment:
"This entry has a direct influence on the existence of other entries. It may add or remove entries.",
type: Array(IncludeIdentifier(AutomaticEntry), { minItems: 1 }),
})
const AutomaticEntryAction = Enum(import.meta.url, {
name: "AutomaticEntryAction",
values: () => ({
Add: EnumCase({ type: null }),
Remove: EnumCase({ type: null }),
}),
})
const AutomaticEntryTarget = Enum(import.meta.url, {
name: "AutomaticEntryTarget",
values: () => ({
Selection: EnumCase({ type: IncludeIdentifier(AutomaticEntryTargetSelection) }),
Fixed: EnumCase({ type: IncludeIdentifier(FixedAutomaticEntryTarget) }),
}),
})
const AutomaticEntryTargetSelection = TypeAlias(import.meta.url, {
name: "AutomaticEntryTargetSelection",
type: () =>
Object({
list: Required({
type: IncludeIdentifier(AutomaticEntryTargetSelectionList),
}),
}),
})
const AutomaticEntryTargetSelectionList = Enum(import.meta.url, {
name: "AutomaticEntryTargetSelectionList",
values: () => ({
MagicalTraditions: EnumCase({ type: null }),
MagicalDilettanteTraditions: EnumCase({ type: null }),
}),
})
const FixedAutomaticEntryTarget = TypeAlias(import.meta.url, {
name: "FixedAutomaticEntryTarget",
type: () =>
Object({
id: Required({
type: IncludeIdentifier(ActivatableIdentifier),
}),
level: Optional({
comment:
"The level of the target entry to be added or removed. If this is omitted for a multi-level entry, level 1 is assumed.",
type: Integer({ minimum: 1 }),
}),
}),
})
+450
View File
@@ -0,0 +1,450 @@
import {
Array,
Boolean,
Enum,
EnumCase,
GenIncludeIdentifier,
GenTypeAlias,
IncludeIdentifier,
Integer,
Object,
Optional,
Param,
Required,
String,
TypeAlias,
TypeArgument,
} from "tsondb/schema/def"
import {
CloseCombatTechniqueIdentifier,
RaceIdentifier,
RangedCombatTechniqueIdentifier,
WeaponIdentifier,
} from "./_Identifier.js"
import {
CombatRelatedSpecialAbilityIdentifier,
CombatTechniqueIdentifier,
} from "./_IdentifierGroup.js"
const CombatSpecialAbilityUsageType = Enum(import.meta.url, {
name: "CombatSpecialAbilityUsageType",
comment: "The definition of how the combat special ability can be used in combat.",
values: () => ({
Passive: EnumCase({ type: null }),
BasicManeuver: EnumCase({ type: null }),
SpecialManeuver: EnumCase({ type: null }),
}),
})
export const usage_type = Required({
comment: "The definition of how the combat special ability can be used in combat.",
type: IncludeIdentifier(CombatSpecialAbilityUsageType),
})
const CombatSpecialAbilityType = Enum(import.meta.url, {
name: "CombatSpecialAbilityType",
comment:
"The definition of if the combat special ability can be used when armed or when unarmed.",
values: () => ({
Armed: EnumCase({ type: null }),
Unarmed: EnumCase({ type: null }),
}),
})
export const type = Required({
comment:
"The definition of if the combat special ability can be used when armed or when unarmed.",
type: IncludeIdentifier(CombatSpecialAbilityType),
})
const Penalty = Enum(import.meta.url, {
name: "Penalty",
comment: "The penalty the special ability gives when used.",
values: () => ({
Single: EnumCase({ type: IncludeIdentifier(SinglePenalty) }),
ByHandedness: EnumCase({ type: IncludeIdentifier(PenaltyByHandedness) }),
ByActivation: EnumCase({ type: IncludeIdentifier(PenaltyByActivation) }),
Selection: EnumCase({ type: IncludeIdentifier(PenaltySelection) }),
ByLevel: EnumCase({ type: IncludeIdentifier(PenaltyByLevel) }),
ByAttack: EnumCase({ type: IncludeIdentifier(PenaltyByAttack) }),
DependsOnHitZone: EnumCase({ type: null }),
}),
})
export const penalty = Optional({
comment: "The penalty the special ability gives when used.",
type: IncludeIdentifier(Penalty),
})
export const penalty_l10n = Optional({
comment: "The penalty the special ability gives when used.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
const SinglePenalty = TypeAlias(import.meta.url, {
name: "SinglePenalty",
type: () =>
Object({
value: Required({
comment: "The penalty value.",
type: Integer(),
}),
applies_to_parry: Optional({
comment: "Set to `true` if the penalty applies to the parry instead of the attack.",
type: Boolean(),
}),
}),
})
const PenaltyByHandedness = TypeAlias(import.meta.url, {
name: "PenaltyByHandedness",
type: () =>
Object({
one_handed: Required({
comment: "The penalty value for one-handed weapons.",
type: Integer(),
}),
two_handed: Required({
comment: "The penalty value for two-handed weapons.",
type: Integer(),
}),
applies_to_parry: Optional({
comment: "Set to `true` if the penalty applies to the parry instead of the attack.",
type: Boolean(),
}),
}),
})
const PenaltyByActivation = TypeAlias(import.meta.url, {
name: "PenaltyByActivation",
type: () =>
Object({
active: Required({
comment: "The penalty value if the entry has been bought by the character.",
type: Integer(),
}),
inactive: Required({
comment: "The penalty value if the entry has not been bought by the character.",
type: Integer(),
}),
applies_to_parry: Optional({
comment: "Set to `true` if the penalty applies to the parry instead of the attack.",
type: Boolean(),
}),
}),
})
const PenaltySelection = TypeAlias(import.meta.url, {
name: "PenaltySelection",
type: () =>
Object({
options: Required({
type: IncludeIdentifier(PenaltySelectionOptions),
}),
}),
})
const PenaltySelectionOptions = Enum(import.meta.url, {
name: "PenaltySelectionOptions",
values: () => ({
Specific: EnumCase({ type: IncludeIdentifier(SpecificPenaltySelectionOptions) }),
Range: EnumCase({ type: IncludeIdentifier(PenaltySelectionOptionsRange) }),
}),
})
const SpecificPenaltySelectionOptions = TypeAlias(import.meta.url, {
name: "SpecificPenaltySelectionOptions",
type: () =>
Object({
list: Required({
comment: "The list of specific penalty options.",
type: Array(IncludeIdentifier(SpecificPenaltySelectionOption), { minItems: 2 }),
}),
}),
})
const SpecificPenaltySelectionOption = TypeAlias(import.meta.url, {
name: "SpecificPenaltySelectionOption",
type: () =>
Object({
value: Required({
comment: "The penalty value.",
type: Integer(),
}),
}),
})
const PenaltySelectionOptionsRange = TypeAlias(import.meta.url, {
name: "PenaltySelectionOptionsRange",
type: () =>
Object({
minimum: Required({
comment: "The minimum penalty value.",
type: Integer(),
}),
maximum: Required({
comment: "The maximum penalty value.",
type: Integer(),
}),
}),
})
const PenaltyByLevel = TypeAlias(import.meta.url, {
name: "PenaltyByLevel",
type: () =>
Object({
levels: Required({
comment:
"A continuous range of penalties for each level. The first element is the penalty for the first level, the second element is the penalty for the second level, and so on.",
type: Array(IncludeIdentifier(PenaltyByLevelLevel), { minItems: 2 }),
}),
external: Optional({
comment:
"The combat-related special ability of which the level defines the penalty instead.",
type: IncludeIdentifier(PenaltyByExternalLevel),
}),
}),
})
const PenaltyByLevelLevel = TypeAlias(import.meta.url, {
name: "PenaltyByLevelLevel",
type: () =>
Object({
value: Required({
comment: "The penalty value for this level.",
type: Integer(),
}),
}),
})
const PenaltyByExternalLevel = TypeAlias(import.meta.url, {
name: "PenaltyByExternalLevel",
comment: "The combat-related special ability of which the level defines the penalty instead.",
type: () =>
Object({
id: Required({
comment:
"The identifier of the combat-related special ability of which the level defines the penalty instead.",
type: IncludeIdentifier(CombatRelatedSpecialAbilityIdentifier),
}),
}),
})
const PenaltyByAttack = TypeAlias(import.meta.url, {
name: "PenaltyByAttack",
type: () =>
Object({
list: Required({
comment:
"A list of penalties for subsequent attacks. The first element is the penalty for the first attack, the second element is the penalty for the second attack, and so on. The order of the first element may be changed using `initial_order`, so that e.g. if set to `2`, the first element is the penalty for the second attack, the second element is the penalty for the third attack, and so on.",
type: Array(IncludeIdentifier(PenaltyByAttackOrderItem), { minItems: 1 }),
}),
initial_order: Optional({
comment: "The order of the first element in the `list` of penalties.",
type: Integer(),
}),
attack_replacement: Optional({
comment:
"Set if a predefined different word should be used instead of the word `attack` for display purposes.",
type: IncludeIdentifier(PenaltyByAttackReplacement),
}),
}),
})
const PenaltyByAttackOrderItem = TypeAlias(import.meta.url, {
name: "PenaltyByAttackOrderItem",
type: () =>
Object({
value: Required({
comment: "The penalty value for this order.",
type: Integer(),
}),
}),
})
const PenaltyByAttackReplacement = Enum(import.meta.url, {
name: "PenaltyByAttackReplacement",
comment:
"Set if a predefined different word should be used instead of the word `attack` for display purposes.",
values: () => ({
Throw: EnumCase({ type: null }),
}),
})
const ApplicableCombatTechniques = Enum(import.meta.url, {
name: "ApplicableCombatTechniques",
values: () => ({
None: EnumCase({ type: null }),
DependingOnCombatStyle: EnumCase({ type: null }),
All: EnumCase({ type: IncludeIdentifier(AllApplicableCombatTechniques) }),
AllClose: EnumCase({ type: IncludeIdentifier(AllApplicableCloseCombatTechniques) }),
AllRanged: EnumCase({ type: IncludeIdentifier(AllApplicableRangedCombatTechniques) }),
Specific: EnumCase({ type: IncludeIdentifier(SpecificApplicableCombatTechniques) }),
}),
})
export const combat_techniques = Required({
comment: "The combat techniques the special ability is applicable to.",
type: IncludeIdentifier(ApplicableCombatTechniques),
})
const AllApplicableCombatTechniques = TypeAlias(import.meta.url, {
name: "AllApplicableCombatTechniques",
type: () =>
Object({
restrictions: Optional({
type: Array(IncludeIdentifier(ApplicableAllCombatTechniquesRestriction), { minItems: 1 }),
}),
}),
})
const AllApplicableCloseCombatTechniques = TypeAlias(import.meta.url, {
name: "AllApplicableCloseCombatTechniques",
type: () =>
Object({
restrictions: Optional({
type: Array(IncludeIdentifier(ApplicableCloseCombatTechniquesRestriction), { minItems: 1 }),
}),
}),
})
const AllApplicableRangedCombatTechniques = TypeAlias(import.meta.url, {
name: "AllApplicableRangedCombatTechniques",
type: () =>
Object({
restrictions: Optional({
type: Array(IncludeIdentifier(ApplicableRangedCombatTechniquesRestriction), {
minItems: 1,
}),
}),
}),
})
const SpecificApplicableCombatTechniques = TypeAlias(import.meta.url, {
name: "SpecificApplicableCombatTechniques",
type: () =>
Object({
list: Required({
type: Array(IncludeIdentifier(SpecificApplicableCombatTechnique), { minItems: 1 }),
}),
}),
})
const SpecificApplicableCombatTechnique = TypeAlias(import.meta.url, {
name: "SpecificApplicableCombatTechnique",
type: () =>
Object({
id: Required({
type: IncludeIdentifier(CombatTechniqueIdentifier),
}),
restrictions: Optional({
type: Array(IncludeIdentifier(ApplicableSpecificCombatTechniquesRestriction), {
minItems: 1,
}),
}),
}),
})
const ApplicableAllCombatTechniquesRestriction = Enum(import.meta.url, {
name: "ApplicableAllCombatTechniquesRestriction",
values: () => ({
Improvised: EnumCase({ type: null }),
PointedBlade: EnumCase({ type: null }),
Mount: EnumCase({ type: null }),
Race: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesRaceRestriction) }),
ExcludeCombatTechniques: EnumCase({
type: GenIncludeIdentifier(ApplicableCombatTechniquesNegativeCombatTechniquesRestriction, [
IncludeIdentifier(CombatTechniqueIdentifier),
]),
}),
}),
})
const ApplicableCloseCombatTechniquesRestriction = Enum(import.meta.url, {
name: "ApplicableCloseCombatTechniquesRestriction",
values: () => ({
Improvised: EnumCase({ type: null }),
PointedBlade: EnumCase({ type: null }),
Mount: EnumCase({ type: null }),
HasParry: EnumCase({ type: null }),
OneHanded: EnumCase({ type: null }),
TwoHanded: EnumCase({ type: null }),
ParryingWeapon: EnumCase({ type: null }),
Race: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesRaceRestriction) }),
ExcludeCombatTechniques: EnumCase({
type: GenIncludeIdentifier(ApplicableCombatTechniquesNegativeCombatTechniquesRestriction, [
CloseCombatTechniqueIdentifier(),
]),
}),
}),
})
const ApplicableRangedCombatTechniquesRestriction = Enum(import.meta.url, {
name: "ApplicableRangedCombatTechniquesRestriction",
values: () => ({
Improvised: EnumCase({ type: null }),
PointedBlade: EnumCase({ type: null }),
Mount: EnumCase({ type: null }),
Race: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesRaceRestriction) }),
ExcludeCombatTechniques: EnumCase({
type: GenIncludeIdentifier(ApplicableCombatTechniquesNegativeCombatTechniquesRestriction, [
RangedCombatTechniqueIdentifier(),
]),
}),
}),
})
const ApplicableSpecificCombatTechniquesRestriction = Enum(import.meta.url, {
name: "ApplicableSpecificCombatTechniquesRestriction",
values: () => ({
Improvised: EnumCase({ type: null }),
PointedBlade: EnumCase({ type: null }),
Mount: EnumCase({ type: null }),
Race: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesRaceRestriction) }),
Level: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesLevelRestriction) }),
Weapons: EnumCase({ type: IncludeIdentifier(ApplicableCombatTechniquesWeaponRestriction) }),
}),
})
const ApplicableCombatTechniquesNegativeCombatTechniquesRestriction = GenTypeAlias(
import.meta.url,
{
name: "ApplicableCombatTechniquesNegativeCombatTechniquesRestriction",
parameters: [Param("Ref")],
type: Ref =>
Object({
list: Required({
comment: "The combat techniques this combat special ability is **not** applicable to.",
type: Array(TypeArgument(Ref), { minItems: 1 }),
}),
}),
},
)
const ApplicableCombatTechniquesRaceRestriction = TypeAlias(import.meta.url, {
name: "ApplicableCombatTechniquesRaceRestriction",
type: () => RaceIdentifier(),
})
const ApplicableCombatTechniquesLevelRestriction = TypeAlias(import.meta.url, {
name: "ApplicableCombatTechniquesLevelRestriction",
type: () =>
Object({
level: Required({
comment: "The combat special ability is only applicable on a certain level.",
type: Integer({ minimum: 1 }),
}),
}),
})
const ApplicableCombatTechniquesWeaponRestriction = TypeAlias(import.meta.url, {
name: "ApplicableCombatTechniquesWeaponRestriction",
type: () =>
Object({
list: Required({
comment: "The specific weapons this combat special ability is only applicable to.",
type: Array(WeaponIdentifier(), { minItems: 1 }),
}),
}),
})
+628
View File
@@ -0,0 +1,628 @@
import {
Array,
Boolean,
Enum,
EnumCase,
IncludeIdentifier,
Integer,
Object,
Optional,
Required,
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedTranslationMap } from "./Locale.js"
import { DurationUnitValue } from "./_ActivatableSkillDuration.js"
import { AspectIdentifier, PropertyIdentifier } from "./_Identifier.js"
import { VolumePointsOptionReferenceIdentifier } from "./_IdentifierGroup.js"
import { ResponsiveText, ResponsiveTextOptional } from "./_ResponsiveText.js"
const EnchantmentCost = Enum(import.meta.url, {
name: "EnchantmentCost",
values: () => ({
ArcaneEnergyCost: EnumCase({ type: IncludeIdentifier(ArcaneEnergyCost) }),
BindingCost: EnumCase({ type: IncludeIdentifier(BindingCost) }),
}),
})
export const cost = Optional({
comment: "The cost when casting the entry.",
type: IncludeIdentifier(EnchantmentCost),
})
export const ArcaneEnergyCost = Enum(import.meta.url, {
name: "ArcaneEnergyCost",
comment: "The AE cost.",
values: () => ({
Fixed: EnumCase({ type: IncludeIdentifier(FixedArcaneEnergyCost) }),
PerCountable: EnumCase({ type: IncludeIdentifier(ArcaneEnergyCostPerCountable) }),
Interval: EnumCase({ type: IncludeIdentifier(IntervalArcaneEnergyCost) }),
ActivationAndHalfInterval: EnumCase({
type: IncludeIdentifier(ActivationAndHalfIntervalArcaneEnergyCost),
}),
Indefinite: EnumCase({ type: IncludeIdentifier(IndefiniteArcaneEnergyCost) }),
Disjunction: EnumCase({ type: IncludeIdentifier(ArcaneEnergyCostDisjunction) }),
Variable: EnumCase({ type: null }),
}),
})
export const aeCost = Optional({
comment: "The AE cost.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
const FixedArcaneEnergyCost = TypeAlias(import.meta.url, {
name: "FixedArcaneEnergyCost",
type: () =>
Object({
value: Required({
comment: "The AE cost value.",
type: Integer({ minimum: 1 }),
}),
is_permanent: Optional({
comment: "Set to `true` if the AE costs are permanent.",
type: Boolean(),
}),
interval: Optional({
comment: "Specified if the AE cost `value` has to be paid for each time interval.",
type: IncludeIdentifier(DurationUnitValue),
}),
per_level: Optional({
comment:
"The AE cost are per level of the enchantment. It may either be displayed in a compressed way (e.g. `1 AE per level`) or in a verbose way (e.g. `1 AE for level I; 2 AE for level II`).",
type: IncludeIdentifier(FixedArcaneEnergyCostPerLevel),
}),
translations: NestedTranslationMap(
Optional,
"FixedArcaneEnergyCost",
Object(
{
note: Optional({
comment: "A note, appended to the generated option string in parenthesis.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 },
),
),
}),
})
const FixedArcaneEnergyCostPerLevel = Enum(import.meta.url, {
name: "FixedArcaneEnergyCostPerLevel",
comment:
"The AE cost are per level of the enchantment. It may either be displayed in a compressed way (e.g. `1 AE per level`) or in a verbose way (e.g. `1 AE for level I; 2 AE for level II`).",
values: () => ({
Compressed: EnumCase({ type: null }),
Verbose: EnumCase({ type: null }),
}),
})
const ArcaneEnergyCostPerCountable = TypeAlias(import.meta.url, {
name: "ArcaneEnergyCostPerCountable",
type: () =>
Object({
value: Required({
comment: "The AE cost value that has to be per a specific countable entity.",
type: Integer({ minimum: 1 }),
}),
base_value: Optional({
comment:
"If defined, in addition to the cost per entity you have to pay a flat amount, regardless of the entity count.",
type: Integer({ minimum: 1 }),
}),
translations: NestedTranslationMap(
Required,
"ArcaneEnergyCostPerCountable",
Object({
per: Required({
comment: "The cost have to be per a specific countable entity, e.g. `8 AE per person`.",
type: IncludeIdentifier(ResponsiveText),
}),
note: Optional({
comment: "A note, appended to the generated string in parenthesis.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
}),
),
}),
})
const IntervalArcaneEnergyCost = TypeAlias(import.meta.url, {
name: "IntervalArcaneEnergyCost",
type: () =>
Object({
value: Required({
comment: "The AE cost value that has to be payed each interval.",
type: Integer({ minimum: 1 }),
}),
interval: Required({
comment: "The time interval for which the AE cost `value` has to be paid.",
type: IncludeIdentifier(DurationUnitValue),
}),
}),
})
const ActivationAndHalfIntervalArcaneEnergyCost = TypeAlias(import.meta.url, {
name: "ActivationAndHalfIntervalArcaneEnergyCost",
type: () =>
Object({
value: Required({
comment:
"The AE cost value that has to be payed for activation. Half of this value has to be payed each interval.",
type: Integer({ minimum: 2, multipleOf: 2 }),
}),
interval: Required({
comment: "The time interval for which the AE cost `value` has to be paid.",
type: IncludeIdentifier(DurationUnitValue),
}),
}),
})
const IndefiniteArcaneEnergyCost = TypeAlias(import.meta.url, {
name: "IndefiniteArcaneEnergyCost",
type: () =>
Object({
modifier: Optional({
comment:
"Specified if the indefinite AP cost description needs to be modified by a certain value.",
type: IncludeIdentifier(IndefiniteArcaneEnergyCostModifier),
}),
translations: NestedTranslationMap(
Required,
"IndefiniteArcaneEnergyCost",
Object({
description: Required({
comment: "A description of where the cost come from.",
type: IncludeIdentifier(ResponsiveText),
}),
}),
),
}),
})
const IndefiniteArcaneEnergyCostModifier = TypeAlias(import.meta.url, {
name: "IndefiniteArcaneEnergyCostModifier",
type: () =>
Object({
arithmetic: Required({
comment: "The arithmetic how to apply the `value` to the `base`.",
type: IncludeIdentifier(IndefiniteArcaneEnergyCostModifierArithmetic),
}),
value: Required({
comment: "The value that is applied to the `base` using the defined `arithmetic`.",
type: Integer({ minimum: 1 }),
}),
}),
})
const IndefiniteArcaneEnergyCostModifierArithmetic = Enum(import.meta.url, {
name: "IndefiniteArcaneEnergyCostModifierArithmetic",
values: () => ({
Add: EnumCase({ type: null }),
}),
})
const ArcaneEnergyCostDisjunction = TypeAlias(import.meta.url, {
name: "ArcaneEnergyCostDisjunction",
type: () =>
Object({
interval: Optional({
comment: "Specified if the selected AE cost option has to be paid for each time interval.",
type: IncludeIdentifier(ArcaneEnergyCostDisjunctionInterval),
}),
options: Required({
comment: "The possible AE cost values.",
type: Array(IncludeIdentifier(ArcaneEnergyCostDisjunctionOption), { minItems: 1 }),
}),
}),
})
const ArcaneEnergyCostDisjunctionInterval = TypeAlias(import.meta.url, {
name: "ArcaneEnergyCostDisjunctionInterval",
type: () =>
Object({
value: Required({
comment: "The interval itself.",
type: IncludeIdentifier(DurationUnitValue),
}),
activation_value: Required({
comment: "The AE cost value for activation.",
type: Integer({ minimum: 1 }),
}),
after_activation: Required({
comment: `Set to \`true\` if the action where the enchantment is casted does not as a part of the first interval that has to be payed, so that the first interval payment needs to be done after the activation.
This works different than other sustained spells, since for them the end of the cast usually already counts as part of the first interval.`,
type: Boolean(),
}),
}),
})
const ArcaneEnergyCostDisjunctionOption = TypeAlias(import.meta.url, {
name: "ArcaneEnergyCostDisjunctionOption",
type: () =>
Object({
value: Required({
comment: "A possible AE cost value.",
type: Integer({ minimum: 1 }),
}),
translations: NestedTranslationMap(
Optional,
"ArcaneEnergyCostDisjunctionOption",
Object(
{
note: Optional({
comment: "A note, appended to the generated option string in parenthesis.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 },
),
),
}),
})
const Volume = Enum(import.meta.url, {
name: "Volume",
comment: "The volume points the enchantment needs.",
values: () => ({
Fixed: EnumCase({ type: IncludeIdentifier(FixedVolume) }),
PerLevel: EnumCase({ type: IncludeIdentifier(VolumePerLevel) }),
ByLevel: EnumCase({ type: IncludeIdentifier(VolumeByLevel) }),
Map: EnumCase({ type: IncludeIdentifier(VolumeMap) }),
DerivedFromSelection: EnumCase({ type: IncludeIdentifier(VolumeDerivedFromSelection) }),
}),
})
export const volume = Required({
comment: "The volume points the enchantment needs.",
type: IncludeIdentifier(Volume),
})
export const volume_l10n = Optional({
comment: "The volume points the enchantment needs.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
const FixedVolume = TypeAlias(import.meta.url, {
name: "FixedVolume",
type: () =>
Object({
points: Required({
comment: "The volume points.",
type: Integer({ minimum: 0 }),
}),
}),
})
const VolumePerLevel = TypeAlias(import.meta.url, {
name: "VolumePerLevel",
type: () =>
Object({
points: Required({
comment: "The volume points per level.",
type: Integer({ minimum: 1 }),
}),
}),
})
const VolumeByLevel = TypeAlias(import.meta.url, {
name: "VolumeByLevel",
type: () =>
Object({
list: Required({
comment:
"The volume points for each level. The first element is the volume points for the first level, the second element is the volume points for the second level, and so on.",
type: Array(IncludeIdentifier(VolumeByLevelItem), { minItems: 2 }),
}),
}),
})
const VolumeByLevelItem = TypeAlias(import.meta.url, {
name: "VolumeByLevelItem",
type: () =>
Object({
points: Required({
comment: "The volume points for this level.",
type: Integer({ minimum: 0 }),
}),
}),
})
const VolumeMap = TypeAlias(import.meta.url, {
name: "VolumeMap",
comment: `A content that is \`3/4/5 Points for Chimera, Daimonid, Golems, Undead / Fairies, Ghosts / Demons, Elementals\` may be respresented as the following map:
\`\`\`yaml
options:
- points: 3
associated_options:
- id:
tag: General
value: # ...
# ...
translations:
en-US:
label: "Chimera, Daimonid, Golems, Undead"
label_standalone: "Chimera/Daimonid/Golems/Undead"
- points: 4
associated_options:
- id:
tag: General
value: # ...
# ...
translations:
en-US:
label: "Fairies, Ghosts"
label_standalone: "Fairies/Ghosts"
- points: 5
associated_options:
- id:
tag: General
value: # ...
# ...
translations:
en-US:
label: "Demons, Elementals"
label_standalone: "Demons/Elementals"
\`\`\`
This will generate the exact same string as seen above. The associated options are not present in the example, but they link to the options the volume specification is meant for.`,
type: () =>
Object({
options: Required({
comment: "The possible costs and associated labels.",
type: Array(IncludeIdentifier(VolumeMapOption), { minItems: 2 }),
}),
translations: NestedTranslationMap(
Optional,
"VolumeMap",
Object(
{
list_prepend: Optional({
comment: "Place a string between the `for` and the grouped map option labels.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
list_append: Optional({
comment: "Place a string after the grouped map option labels.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
replacement: Optional({
comment:
"If the string from the book cannot be generated using the default generation technique, use this string. All options still need to be inserted propertly, since it may be used by in-game tools to provide a selection to players.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 },
),
),
}),
})
const VolumeMapOption = TypeAlias(import.meta.url, {
name: "VolumeMapOption",
type: () =>
Object({
points: Required({
comment: "The full permanent AE cost value for this option.",
type: Integer({ minimum: 1 }),
}),
associated_options: Required({
comment: "Links to the options this volume specification is meant for.",
type: Array(IncludeIdentifier(VolumeMapOptionAssociatedOption), { minItems: 1 }),
}),
translations: NestedTranslationMap(
Optional,
"VolumeMapOption",
Object(
{
label: Required({
comment: "The description of the option for cost string generation.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
label_standalone: Optional({
comment:
"The description of the option if used standalone. Only used if different from `label`.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 },
),
),
}),
})
const VolumeMapOptionAssociatedOption = TypeAlias(import.meta.url, {
name: "VolumeMapOptionAssociatedOption",
type: () =>
Object({
id: Required({
comment: "The options identifier.",
type: IncludeIdentifier(VolumePointsOptionReferenceIdentifier),
}),
}),
})
const VolumeDerivedFromSelection = TypeAlias(import.meta.url, {
name: "VolumeDerivedFromSelection",
type: () =>
Object({
fallback: Required({
comment: "The volume for the selection if it does not define a specific one.",
type: Integer({ minimum: 0 }),
}),
}),
})
export const BindingCost = Enum(import.meta.url, {
name: "BindingCost",
comment: "The binding cost for an enchantment.",
values: () => ({
Fixed: EnumCase({ type: IncludeIdentifier(FixedBindingCost) }),
PerLevel: EnumCase({ type: IncludeIdentifier(BindingCostPerLevel) }),
Map: EnumCase({ type: IncludeIdentifier(BindingCostMap) }),
DerivedFromSelection: EnumCase({ type: IncludeIdentifier(BindingCostDerivedFromSelection) }),
}),
})
export const bindingCost = Optional({
comment: "The binding cost for an enchantment.",
isDeprecated: true,
type: String({ minLength: 1 }),
})
const FixedBindingCost = TypeAlias(import.meta.url, {
name: "FixedBindingCost",
type: () =>
Object({
permanent_value: Required({
comment: `The permanent AE cost.
If the enchantment has multiple levels, it is only applied for the first level.`,
type: Integer({ minimum: 1 }),
}),
}),
})
const BindingCostPerLevel = TypeAlias(import.meta.url, {
name: "BindingCostPerLevel",
type: () =>
Object({
permanent_value: Required({
comment: "The permanent AE cost per level.",
type: Integer({ minimum: 1 }),
}),
}),
})
const BindingCostMap = TypeAlias(import.meta.url, {
name: "BindingCostMap",
comment: `A content that is \`2/4/8 permanent AE for spell-swords with the combat technique Daggers, Swords, or Two-Handed Swords\` may be respresented as the following map:
\`\`\`yaml
options:
- permanent_value: 2
translations:
en-US:
label: "Daggers"
label_standalone: "Dagger"
- permanent_value: 4
translations:
en-US:
label: "Swords"
label_standalone: "Sword"
- permanent_value: 8
translations:
en-US:
label: "Two-Handed Swords"
label_standalone: "Two-Handed Sword"
list_prepend: "spell-swords with the combat technique"
\`\`\`
This will generate the exact same string as seen above.`,
type: () =>
Object({
options: Required({
comment: "The possible costs and associated labels.",
type: Array(IncludeIdentifier(BindingCostMapOption), { minItems: 2 }),
}),
translations: NestedTranslationMap(
Optional,
"BindingCostMap",
Object(
{
list_prepend: Optional({
comment: "Place a string between the `for` and the grouped map option labels.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
list_append: Optional({
comment: "Place a string after the grouped map option labels.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
replacement: Optional({
comment:
"If the string from the book cannot be generated using the default generation technique, use this string. All options still need to be inserted propertly, since it may be used by in-game tools to provide a selection to players.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 },
),
),
}),
})
const BindingCostMapOption = TypeAlias(import.meta.url, {
name: "BindingCostMapOption",
type: () =>
Object({
permanent_value: Required({
comment: "The full permanent AE cost value for this option.",
type: Integer({ minimum: 1 }),
}),
translations: NestedTranslationMap(
Optional,
"BindingCostMapOption",
Object({
label: Required({
comment: "The description of the option for cost string generation.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
label_standalone: Optional({
comment:
"The description of the option if used standalone. Only used if different from `label`.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
}),
),
}),
})
const BindingCostDerivedFromSelection = TypeAlias(import.meta.url, {
name: "BindingCostDerivedFromSelection",
type: () =>
Object({
fallback: Required({
comment: "The binding cost for the selection if it does not define a specific one.",
type: Integer({ minimum: 0 }),
}),
}),
})
const PropertyDeclaration = Enum(import.meta.url, {
name: "PropertyDeclaration",
comment:
"The magic propertys identifier. `DependingOnProperty` can only be used if the special ability has an option to select a property.",
values: () => ({
DependingOnSelection: EnumCase({ type: null }),
Fixed: EnumCase({ type: PropertyIdentifier() }),
}),
})
export const property = () =>
Required({
comment:
"The magic propertys identifier. `DependingOnProperty` can only be used if the special ability has an option to select a property.",
type: IncludeIdentifier(PropertyDeclaration),
})
export const propertyOptional = () =>
Optional({
comment:
"The magic propertys identifier. `DependingOnProperty` can only be used if the special ability has an option to select a property.",
type: IncludeIdentifier(PropertyDeclaration),
})
export const aspect = () =>
Required({
comment: "The blessed aspect.",
type: AspectIdentifier(),
})
export const aspectOptional = () =>
Optional({
comment: "The blessed aspect.",
type: AspectIdentifier(),
})
@@ -16,7 +16,7 @@ import {
TypeAlias,
TypeArgument,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import {
CeremonyIdentifier,
CloseCombatTechniqueIdentifier,
@@ -285,16 +285,16 @@ const SkillApplicationOrUse = TypeAlias(import.meta.url, {
comment: "The applications or uses identifier. An entry-unique, increasing integer.",
type: Integer({ minimum: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"SkillApplicationOrUseTranslation",
"SkillApplicationOrUse",
Object({
name: Required({
comment:
"The name of the application or use if different from the activatable entrys name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
})
@@ -424,7 +424,7 @@ const SelectOptionsFixedAdventurePointsValue = GenTypeAlias(import.meta.url, {
type: Array(
GenIncludeIdentifier(SelectOptionsFixedAdventurePointsValueMapping, [
TypeArgument(Identifier),
])
]),
),
}),
default: Required({
+333
View File
@@ -0,0 +1,333 @@
import {
Boolean,
ChildEntities,
Entity,
Enum,
EnumCase,
getAnyEnumCaseValue,
IncludeIdentifier,
Integer,
Object,
Optional,
Required,
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedTranslationMap } from "./Locale.js"
import { SelectOptionCategory } from "./_ActivatableSelectOptionCategory.js"
import { NewSkillApplication, SkillUse } from "./_ActivatableSkillApplicationsAndUses.js"
import { GeneralIdentifier, SkillIdentifier } from "./_Identifier.js"
import { CombatTechniqueIdentifier, SelectOptionParentIdentifier } from "./_IdentifierGroup.js"
import { GeneralPrerequisites } from "./_Prerequisite.js"
import { Errata } from "./source/_Erratum.js"
import { optionalSrc } from "./source/_PublicationRef.js"
const SelectOptions = TypeAlias(import.meta.url, {
name: "SelectOptions",
comment: `Definitions for possible options for the activatable entry. They can either be derived from entry categories or be defined explicitly. Both can happen as well, but if there is an explicitly defined select option and a derived select option has the same identifier (which may only happen if skill or combat technique identifiers are used for explicit select options), the explicit definition overwrites the derived option.
Note that this is only a full definition of options for simple logic that can be made explicit using the more detailed configuration for both derived categories and explicit options. There are quite a few entries whose option logic cannot be fully represented here, so that it needs to be implemented manually.`,
type: () =>
Object(
{
derived: Optional({
comment: `An entry category with optional further configuration. All available entries from the specified categories will be included as separate select options. You can also specify a set of groups that should only be included. Groups not mentioned will be excluded then.`,
type: IncludeIdentifier(SelectOptionCategory),
}),
},
{ minProperties: 1 },
),
})
export const select_options = Optional({
comment: `Definitions for possible options for the activatable entry, derived from entry categories.
Note that this is only a full definition of options for simple logic that can be made explicit using the more detailed configuration for both derived categories and explicit options. There are quite a few entries whose option logic cannot be fully represented here, so that it needs to be implemented manually.`,
type: IncludeIdentifier(SelectOptions),
})
export const ExplicitSelectOption = Enum(import.meta.url, {
name: "ExplicitSelectOption",
values: () => ({
General: EnumCase({ type: GeneralIdentifier() }),
Skill: EnumCase({ type: IncludeIdentifier(ExplicitSkillSelectOption) }),
CombatTechnique: EnumCase({ type: IncludeIdentifier(ExplicitCombatTechniqueSelectOption) }),
}),
})
export const GeneralSelectOption = Entity(import.meta.url, {
name: "GeneralSelectOption",
namePlural: "GeneralSelectOptions",
type: () =>
Object({
parent: Required({
comment: "The entry that contains select option.",
type: IncludeIdentifier(SelectOptionParentIdentifier),
}),
profession_only: Optional({
comment:
"Sometimes, professions use specific text selections that are not contained in described lists. This ensures you can use them for professions only. They are not going to be displayed as options to the user.",
type: Boolean(),
}),
skill_applications: Required({
comment:
"Registers new applications, which get enabled once this entry is activated with its respective select option. It specifies an entry-unique identifier and the skill it belongs to. A translation can be left out if its name equals the name of the origin select option.",
type: ChildEntities(NewSkillApplication),
}),
skill_uses: Required({
comment:
"Registers uses, which get enabled once this entry is activated with its respective select option. It specifies an entry-unique identifier and the skill it belongs to. A translation can be left out if its name equals the name of the origin select option.",
type: ChildEntities(SkillUse),
}),
prerequisites: Optional({
comment: "Prerequisites for the select option.",
type: IncludeIdentifier(GeneralPrerequisites),
}),
binding_cost: Optional({
comment:
"Specific binding cost for the select option. Only has an effect if the associated entry supports binding costs.",
type: Integer({ minimum: 0 }),
}),
ap_value: Optional({
comment: "Specific AP cost for the select option.",
type: Integer({ minimum: 1 }),
}),
src: optionalSrc,
translations: NestedTranslationMap(
Required,
"GeneralSelectOption",
Object({
name: Required({
comment: "The name of the select option.",
type: String({ minLength: 1 }),
}),
name_in_profession: Optional({
comment: "The name of the select option when displayed in a generated profession text.",
type: String({ minLength: 1 }),
}),
description: Optional({
comment:
"The description of the select option. Useful for Bad Habits, Trade Secrets and other entries where a description is available.",
type: String({ minLength: 1, isMarkdown: true }),
}),
errata: Optional({
type: IncludeIdentifier(Errata),
}),
}),
),
}),
parentReferenceKey: "parent",
displayName: {},
displayNameCustomizer: ({
instance,
instanceDisplayName,
instanceDisplayNameLocaleId,
getDisplayNameForInstanceId,
}) => ({
name: `${
instanceDisplayName.length > 0
? instanceDisplayName
: (getDisplayNameForInstanceId(getAnyEnumCaseValue(instance.parent))?.name ?? "")
}${instanceDisplayName}`,
localeId: instanceDisplayNameLocaleId,
}),
})
export const explicit_select_options = Required({
comment: `Explicit definitions for possible options for the activatable entry.
Note that this is only a full definition of options for simple logic that can be made explicit using the more detailed configuration for both derived categories and explicit options. There are quite a few entries whose option logic cannot be fully represented here, so that it needs to be implemented manually.`,
type: ChildEntities(GeneralSelectOption),
})
const ExplicitSkillSelectOption = TypeAlias(import.meta.url, {
name: "ExplicitSkillSelectOption",
type: () =>
Object({
id: Required({
comment: "The skills identifier.",
type: SkillIdentifier(),
}),
prerequisites: Optional({
comment: "Prerequisites for the select option.",
type: IncludeIdentifier(GeneralPrerequisites),
}),
binding_cost: Optional({
comment:
"Specific binding cost for the select option. Only has an effect if the associated entry supports binding costs.",
type: Integer({ minimum: 0 }),
}),
ap_value: Optional({
comment: "Specific AP cost for the select option.",
type: Integer({ minimum: 1 }),
}),
src: optionalSrc,
translations: NestedTranslationMap(
Optional,
"ExplicitSkillSelectOption",
Object(
{
errata: Optional({
type: IncludeIdentifier(Errata),
}),
},
{ minProperties: 1 },
),
),
}),
})
const ExplicitCombatTechniqueSelectOption = TypeAlias(import.meta.url, {
name: "ExplicitCombatTechniqueSelectOption",
type: () =>
Object({
id: Required({
comment: "The combat techniques identifier.",
type: IncludeIdentifier(CombatTechniqueIdentifier),
}),
prerequisites: Optional({
comment: "Prerequisites for the select option.",
type: IncludeIdentifier(GeneralPrerequisites),
}),
binding_cost: Optional({
comment:
"Specific binding cost for the select option. Only has an effect if the associated entry supports binding costs.",
type: Integer({ minimum: 0 }),
}),
ap_value: Optional({
comment: "Specific AP cost for the select option.",
type: Integer({ minimum: 1 }),
}),
src: optionalSrc,
translations: NestedTranslationMap(
Optional,
"ExplicitCombatTechniqueSelectOption",
Object(
{
errata: Optional({
type: IncludeIdentifier(Errata),
}),
},
{ minProperties: 1 },
),
),
}),
})
// "Options": {
// "$comment": "Unfinished generalized options handling.",
// "type": "array",
// "minItems": 1,
// "items": {
// "oneOf": [
// {
// "type": "object",
// "properties": {
// "type": { "const": "List" },
// "value": {
// "type": "object",
// "properties": {
// "list": {
// "oneOf": [
// {
// "type": "object",
// "properties": {
// "type": { "const": "Generate" },
// "value": { "$ref": "#/definitions/SelectOptionCategories" }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// },
// {
// "type": "object",
// "properties": {
// "type": { "const": "Explicit" },
// "value": { "$ref": "#/definitions/SelectOptions" }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// }
// ]
// },
// "multiple": {
// "description": "Sometimes, multiple options from a single list have to or can be chosen. Its possible to define a number of options that have to be selected or a range of numbers of options that can be selected.",
// "oneOf": [
// {
// "type": "object",
// "properties": {
// "type": { "const": "Fixed" },
// "value": {
// "description": "The number of options that have to be selected.",
// "type": "integer",
// "minimum": 2
// }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// },
// {
// "type": "object",
// "properties": {
// "type": { "const": "Range" },
// "value": {
// "type": "object",
// "properties": {
// "min": {
// "description": "The minimum number of options that need to be selected. If left empty it defaults to 1.",
// "type": "integer",
// "minimum": 2
// },
// "max": {
// "description": "The maximum number of options that need to be selected.",
// "type": "integer",
// "minimum": 2
// }
// },
// "required": ["max"],
// "additionalProperties": false
// }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// }
// ]
// }
// },
// "required": ["list"],
// "additionalProperties": false
// }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// },
// {
// "type": "object",
// "properties": {
// "type": { "const": "TextInput" },
// "value": {
// "type": "object",
// "properties": {
// "label": {
// "type": "object",
// "description": "All translations for the entry, identified by IETF language tag (BCP47).",
// "patternProperties": {
// "^[a-z]{2}-[A-Z]{2}$": {
// "description": "The text input label.",
// "type": "string",
// "minLength": 1
// }
// },
// "minProperties": 1,
// "additionalProperties": false
// }
// },
// "required": ["label"],
// "additionalProperties": false
// }
// },
// "required": ["type", "value"],
// "additionalProperties": false
// }
// ]
// }
// },
@@ -0,0 +1,121 @@
import {
Array,
ChildEntities,
Entity,
getAnyEnumCaseValue,
IncludeIdentifier,
Integer,
Object,
Optional,
Required,
String,
} from "tsondb/schema/def"
import { NestedTranslationMap } from "./Locale.js"
import { SkillIdentifier } from "./_Identifier.js"
import { NewSkillApplicationOrUseParentIdentifier } from "./_IdentifierGroup.js"
export const NewSkillApplication = Entity(import.meta.url, {
name: "NewSkillApplication",
namePlural: "NewSkillApplications",
comment:
"Registers new skill applications, which get enabled once the referenced entry is activated. It specifies the skill(s) it belongs to. A translation can be left out if its name equals the name of the origin activatable entry.",
type: () =>
Object({
parent: Required({
comment: "The entry that enables the new skill application.",
type: IncludeIdentifier(NewSkillApplicationOrUseParentIdentifier),
}),
skills: Required({
comment: "The skill(s) this application belongs to.",
type: Array(SkillIdentifier(), { minItems: 1 }),
}),
required_skill_rating: Optional({
comment:
"If an application applies to multiple skills, it may need to ensure the respective skill is on a certain skill rating if the activatable entry cannot ensure this prerequisite.",
type: Integer({ minimum: 1 }),
}),
translations: NestedTranslationMap(
Optional,
"NewSkillApplication",
Object({
name: Required({
comment: "The name of the application if different from the activatable entrys name.",
type: String({ minLength: 1 }),
}),
}),
),
}),
parentReferenceKey: "parent",
displayName: {},
displayNameCustomizer: ({
instance,
instanceDisplayName,
instanceDisplayNameLocaleId,
getDisplayNameForInstanceId,
}) => ({
name:
instanceDisplayName.length > 0
? instanceDisplayName
: (getDisplayNameForInstanceId(getAnyEnumCaseValue(instance.parent))?.name ?? ""),
localeId: instanceDisplayNameLocaleId,
}),
})
export const skill_applications = Required({
comment:
"Registers new skill applications, which get enabled once this entry is activated. It specifies an entry-unique identifier and the skill it belongs to. A translation can be left out if its name equals the name of the origin activatable entry.",
type: ChildEntities(NewSkillApplication),
})
export const SkillUse = Entity(import.meta.url, {
name: "SkillUse",
namePlural: "SkillUses",
comment:
"Registers uses, which get enabled once this entry is activated. It specifies the skill(s) it belongs to. A translation can be left out if its name equals the name of the origin activatable entry.",
type: () =>
Object({
parent: Required({
comment: "The entry that enables the use.",
type: IncludeIdentifier(NewSkillApplicationOrUseParentIdentifier),
}),
skills: Required({
comment: "The skill(s) this use belongs to.",
type: Array(SkillIdentifier(), { minItems: 1 }),
}),
required_skill_rating: Optional({
comment:
"If a use applies to multiple skills, it may need to ensure the respective skill is on a certain skill rating if the activatable entry cannot ensure this prerequisite.",
type: Integer({ minimum: 1 }),
}),
translations: NestedTranslationMap(
Optional,
"SkillUse",
Object({
name: Required({
comment: "The name of the use if different from the activatable entrys name.",
type: String({ minLength: 1 }),
}),
}),
),
}),
parentReferenceKey: "parent",
displayName: {},
displayNameCustomizer: ({
instance,
instanceDisplayName,
instanceDisplayNameLocaleId,
getDisplayNameForInstanceId,
}) => ({
name:
instanceDisplayName.length > 0
? instanceDisplayName
: (getDisplayNameForInstanceId(getAnyEnumCaseValue(instance.parent))?.name ?? ""),
localeId: instanceDisplayNameLocaleId,
}),
})
export const skill_uses = Required({
comment:
"Registers uses, which get enabled once this entry is activated. It specifies an entry-unique identifier and the skill it belongs to. A translation can be left out if its name equals the name of the origin activatable entry.",
type: ChildEntities(SkillUse),
})
+1 -1
View File
@@ -55,7 +55,7 @@ There must always be at least one casting time value.`,
type: IncludeIdentifier(CastingTimeDuringLovemaking),
}),
},
{ minProperties: 1 }
{ minProperties: 1 },
),
})
+23 -23
View File
@@ -13,7 +13,7 @@ import {
import { DurationUnitValue } from "./_ActivatableSkillDuration.js"
import { SkillModificationLevelIdentifier } from "./_Identifier.js"
import { ResponsiveText, ResponsiveTextOptional, ResponsiveTextReplace } from "./_ResponsiveText.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const OneTimeCost = Enum(import.meta.url, {
name: "OneTimeCost",
@@ -54,15 +54,15 @@ const ModifiableOneTimeCost = TypeAlias(import.meta.url, {
comment: "The part of the cost value that has to be spent permanently.",
type: Integer({ minimum: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"ModifiableOneTimeCostTranslation",
"ModifiableOneTimeCost",
Object({
replacement: Required({
comment: "A replacement string.",
type: IncludeIdentifier(ResponsiveTextReplace),
}),
})
}),
),
}),
})
@@ -87,15 +87,15 @@ const NonModifiableOneTimeCost = TypeAlias(import.meta.url, {
comment: "The cost have to be per a specific countable entity, e.g. `8 KP per person`.",
type: IncludeIdentifier(NonModifiableOneTimeCostPerCountable),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"NonModifiableOneTimeCostTranslation",
"NonModifiableOneTimeCost",
Object({
note: Required({
comment: "A note, appended to the generated string in parenthesis.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
})
}),
),
}),
})
@@ -108,15 +108,15 @@ const NonModifiableOneTimeCostPerCountable = TypeAlias(import.meta.url, {
comment: "If defined, the minimum total AE that have to be spent casting the skill.",
type: Integer({ minimum: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"NonModifiableOneTimeCostPerCountableTranslation",
"NonModifiableOneTimeCostPerCountable",
Object({
countable: Required({
comment: "The countable entity name.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
@@ -125,15 +125,15 @@ export const IndefiniteOneTimeCost = TypeAlias(import.meta.url, {
name: "IndefiniteOneTimeCost",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IndefiniteOneTimeCostTranslation",
"IndefiniteOneTimeCost",
Object({
description: Required({
comment: "A description of where the cost come from.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
@@ -164,9 +164,9 @@ This will generate the exact same string as seen above given it is set for a
minItems: 2,
}),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"CostMapTranslation",
"CostMap",
Object(
{
list_prepend: Optional({
@@ -183,8 +183,8 @@ This will generate the exact same string as seen above given it is set for a
type: IncludeIdentifier(ResponsiveTextOptional),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
@@ -201,9 +201,9 @@ const CostMapOption = TypeAlias(import.meta.url, {
comment: "The part of the `value` that has to be paid permanently.",
type: Integer({ minimum: 0 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"CostMapOptionTranslation",
"CostMapOption",
Object({
label: Required({
comment: "The description of the option for cost string generation.",
@@ -214,7 +214,7 @@ const CostMapOption = TypeAlias(import.meta.url, {
"The description of the option if used standalone. Only used if different from `label`.",
type: IncludeIdentifier(ResponsiveTextOptional),
}),
})
}),
),
}),
})
@@ -277,15 +277,15 @@ const NonModifiableSustainedCostPerCountable = TypeAlias(import.meta.url, {
comment: "If defined, the minimum total AE that have to be spent casting the skill.",
type: Integer({ minimum: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"NonModifiableSustainedCostPerCountableTranslation",
"NonModifiableSustainedCostPerCountable",
Object({
countable: Required({
comment: "The countable entity name.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
+17 -17
View File
@@ -11,7 +11,7 @@ import {
} from "tsondb/schema/def"
import { CheckResultBasedModifier, CheckResultValue } from "./_ActivatableSkillCheckResultBased.js"
import { ResponsiveText, ResponsiveTextReplace } from "./_ResponsiveText.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const DurationForOneTime = Enum(import.meta.url, {
name: "DurationForOneTime",
@@ -32,15 +32,15 @@ const Immediate = TypeAlias(import.meta.url, {
comment: "Specified if the duration has a maximum time span.",
type: IncludeIdentifier(DurationUnitValue),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"ImmediateTranslation",
"Immediate",
Object({
replacement: Optional({
comment: "A replacement string.",
type: IncludeIdentifier(ResponsiveTextReplace),
}),
})
}),
),
}),
})
@@ -49,15 +49,15 @@ const PermanentDuration = TypeAlias(import.meta.url, {
name: "PermanentDuration",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"PermanentDurationTranslation",
"PermanentDuration",
Object({
replacement: Optional({
comment: "A replacement string.",
type: IncludeIdentifier(ResponsiveTextReplace),
}),
})
}),
),
}),
})
@@ -78,9 +78,9 @@ export const FixedDuration = TypeAlias(import.meta.url, {
comment: "The duration unit.",
type: IncludeIdentifier(DurationUnit),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"FixedDurationTranslation",
"FixedDuration",
Object(
{
replacement: Optional({
@@ -88,8 +88,8 @@ export const FixedDuration = TypeAlias(import.meta.url, {
type: IncludeIdentifier(ResponsiveTextReplace),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
@@ -114,15 +114,15 @@ export const CheckResultBasedDuration = TypeAlias(import.meta.url, {
comment: "The duration unit.",
type: IncludeIdentifier(DurationUnit),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"CheckResultBasedDurationTranslation",
"CheckResultBasedDuration",
Object({
replacement: Optional({
comment: "A replacement string.",
type: IncludeIdentifier(ResponsiveTextReplace),
}),
})
}),
),
}),
})
@@ -131,15 +131,15 @@ export const IndefiniteDuration = TypeAlias(import.meta.url, {
name: "IndefiniteDuration",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IndefiniteDurationTranslation",
"IndefiniteDuration",
Object({
description: Required({
comment: "A description of the duration.",
type: IncludeIdentifier(ResponsiveText),
}),
})
}),
),
}),
})
+5 -5
View File
@@ -12,7 +12,7 @@ import {
import { CheckResultBasedModifier, CheckResultValue } from "./_ActivatableSkillCheckResultBased.js"
import { SkillModificationLevelIdentifier } from "./_Identifier.js"
import { ResponsiveTextOptional, ResponsiveTextReplace } from "./_ResponsiveText.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
export const Range = TypeAlias(import.meta.url, {
name: "Range",
@@ -22,9 +22,9 @@ export const Range = TypeAlias(import.meta.url, {
comment: "The range value.",
type: IncludeIdentifier(RangeValue),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"RangeTranslation",
"Range",
Object(
{
note: Optional({
@@ -38,8 +38,8 @@ export const Range = TypeAlias(import.meta.url, {
type: IncludeIdentifier(ResponsiveTextReplace),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
+4 -4
View File
@@ -9,7 +9,7 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { TargetCategoryIdentifier } from "./_Identifier.js"
export const AffectedTargetCategories = TypeAlias(import.meta.url, {
@@ -25,12 +25,12 @@ const SpecificAffectedTargetCategory = TypeAlias(import.meta.url, {
type: () =>
Object({
id: Required({ type: IncludeIdentifier(SpecificAffectedTargetCategoryIdentifier) }),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"SpecificAffectedTargetCategoryTranslation",
"SpecificAffectedTargetCategory",
Object({
note: Required({ type: String({ minLength: 1 }) }),
})
}),
),
}),
})
+3 -3
View File
@@ -1,5 +1,5 @@
import { IncludeIdentifier, Object, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { ArcaneTraditionPrerequisites } from "./_Prerequisite.js"
export const ArcaneTraditionType =
@@ -9,7 +9,7 @@ export const ArcaneTraditionType =
prerequisites: Required({
type: IncludeIdentifier(ArcaneTraditionPrerequisites),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
translationName,
Object({
@@ -17,6 +17,6 @@ export const ArcaneTraditionType =
comment: "The arcane traditions name.",
type: String({ minLength: 1 }),
}),
})
}),
),
})
+4 -4
View File
@@ -18,7 +18,7 @@ import {
TypeAlias,
TypeArgument,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { AlternativeName } from "./_AlternativeNames.js"
import { Errata } from "./source/_Erratum.js"
@@ -55,9 +55,9 @@ export const Cause = TypeAlias(import.meta.url, {
"The chance to get infected by this cause, in percent. Some causes do not have a specific chance.",
type: Integer({ minimum: 5, multipleOf: 5, maximum: 100 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"CauseTranslation",
"Cause",
Object({
name: Required({
comment: "The causes name.",
@@ -72,7 +72,7 @@ export const Cause = TypeAlias(import.meta.url, {
comment: "An additional note about this cause.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
})
+8 -7
View File
@@ -5,6 +5,7 @@
import {
ChildEntities,
Entity,
getAnyEnumCaseValue,
IncludeIdentifier,
Integer,
Object,
@@ -15,7 +16,7 @@ import {
} from "tsondb/schema/def"
import { SkillWithEnhancementsIdentifier } from "./_IdentifierGroup.js"
import { EnhancementPrerequisites } from "./_Prerequisite.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { PublicationRefs } from "./source/_PublicationRef.js"
@@ -56,9 +57,9 @@ export const Enhancement = Entity(import.meta.url, {
comment: "Only defined if different than the associated skill.",
type: IncludeIdentifier(PublicationRefs),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"EnhancementTranslation",
"Enhancement",
Object({
name: Required({
comment: "The name of the enhancement.",
@@ -76,7 +77,7 @@ export const Enhancement = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
parentReferenceKey: "parent",
@@ -87,9 +88,9 @@ export const Enhancement = Entity(import.meta.url, {
instanceDisplayNameLocaleId,
getDisplayNameForInstanceId,
}) => ({
name: `${getDisplayNameForInstanceId(
(instance as any).parent[(instance as any).parent.kind]
)}${instanceDisplayName}`,
name: `${
getDisplayNameForInstanceId(getAnyEnumCaseValue(instance.parent))?.name ?? ""
}${instanceDisplayName}`,
localeId: instanceDisplayNameLocaleId,
}),
})
+8 -1
View File
@@ -1,5 +1,6 @@
import { ReferenceIdentifier as R } from "tsondb/schema/def"
import { GeneralSelectOption, NewSkillApplication, SkillUse } from "./_Activatable.js"
import { GeneralSelectOption } from "./_ActivatableSelectOptions.js"
import { NewSkillApplication, SkillUse } from "./_ActivatableSkillApplicationsAndUses.js"
import { Enhancement } from "./_Enhancements.js"
import { Influence } from "./_Influence.js"
import { Advantage } from "./Advantage.js"
@@ -39,6 +40,7 @@ import { Liebesspielzeug } from "./equipment/item/Liebesspielzeug.js"
import { LuxuryGood } from "./equipment/item/LuxuryGood.js"
import { MagicalArtifact } from "./equipment/item/MagicalArtifact.js"
import { MusicalInstrument } from "./equipment/item/MusicalInstrument.js"
import { Newspaper } from "./equipment/item/Newspaper.js"
import { OrienteeringAid } from "./equipment/item/OrienteeringAid.js"
import { Poison } from "./equipment/item/Poison.js"
import { RopeOrChain } from "./equipment/item/RopeOrChain.js"
@@ -80,6 +82,8 @@ import {
import { Property } from "./Property.js"
import { Race, RaceVariant } from "./Race.js"
import { Ritual } from "./Ritual.js"
import { AlternativeRule } from "./rule/AlternativeRule.js"
import { PlayerType } from "./rule/AlternativeRule_PlayerType.js"
import { CoreRule } from "./rule/CoreRule.js"
import { FocusRule } from "./rule/FocusRule.js"
import { Subject } from "./rule/FocusRule_Subject.js"
@@ -155,6 +159,7 @@ export const AdvancedMagicalSpecialAbilityIdentifier: () => R = () =>
R(AdvancedMagicalSpecialAbility)
export const AdvancedSkillSpecialAbilityIdentifier: () => R = () => R(AdvancedSkillSpecialAbility)
export const AdvantageIdentifier: () => R = () => R(Advantage)
export const AlternativeRuleIdentifier: () => R = () => R(AlternativeRule)
export const AmmunitionIdentifier: () => R = () => R(Ammunition)
export const AncestorGlyphIdentifier: () => R = () => R(AncestorGlyph)
export const AnimalCareIdentifier: () => R = () => R(AnimalCare)
@@ -249,6 +254,7 @@ export const MagicalSpecialAbilityIdentifier: () => R = () => R(MagicalSpecialAb
export const MagicalTraditionIdentifier: () => R = () => R(MagicalTradition)
export const MagicStyleSpecialAbilityIdentifier: () => R = () => R(MagicStyleSpecialAbility)
export const MusicalInstrumentIdentifier: () => R = () => R(MusicalInstrument)
export const NewspaperIdentifier: () => R = () => R(Newspaper)
export const OptionalRuleIdentifier: () => R = () => R(OptionalRule)
export const OrbEnchantmentIdentifier: () => R = () => R(OrbEnchantment)
export const OrienteeringAidIdentifier: () => R = () => R(OrienteeringAid)
@@ -259,6 +265,7 @@ export const PactTypeIdentifier: () => R = () => R(PactType)
export const PatronCategoryIdentifier: () => R = () => R(PatronCategory)
export const PatronIdentifier: () => R = () => R(Patron)
export const PersonalityTraitIdentifier: () => R = () => R(PersonalityTrait)
export const PlayerTypeIdentifier: () => R = () => R(PlayerType)
export const PoisonIdentifier: () => R = () => R(Poison)
export const ProfessionIdentifier: () => R = () => R(Profession)
export const ProfessionPackageIdentifier: () => R = () => R(ProfessionPackage)
+64
View File
@@ -80,6 +80,7 @@ import {
MagicalTraditionIdentifier,
MagicStyleSpecialAbilityIdentifier,
MusicalInstrumentIdentifier,
NewspaperIdentifier,
OptionalRuleIdentifier,
OrbEnchantmentIdentifier,
OrienteeringAidIdentifier,
@@ -416,6 +417,65 @@ export const SelectOptionIdentifier = Enum(import.meta.url, {
}),
})
export const SelectOptionParentIdentifier = Enum(import.meta.url, {
name: "SelectOptionParentIdentifier",
values: () => ({
Advantage: EnumCase({ type: AdvantageIdentifier() }),
Disadvantage: EnumCase({ type: DisadvantageIdentifier() }),
AdvancedCombatSpecialAbility: EnumCase({ type: AdvancedCombatSpecialAbilityIdentifier() }),
AdvancedKarmaSpecialAbility: EnumCase({ type: AdvancedKarmaSpecialAbilityIdentifier() }),
AdvancedMagicalSpecialAbility: EnumCase({ type: AdvancedMagicalSpecialAbilityIdentifier() }),
AdvancedSkillSpecialAbility: EnumCase({ type: AdvancedSkillSpecialAbilityIdentifier() }),
AncestorGlyph: EnumCase({ type: AncestorGlyphIdentifier() }),
ArcaneOrbEnchantment: EnumCase({ type: ArcaneOrbEnchantmentIdentifier() }),
AttireEnchantment: EnumCase({ type: AttireEnchantmentIdentifier() }),
BlessedTradition: EnumCase({ type: BlessedTraditionIdentifier() }),
BowlEnchantment: EnumCase({ type: BowlEnchantmentIdentifier() }),
BrawlingSpecialAbility: EnumCase({ type: BrawlingSpecialAbilityIdentifier() }),
CauldronEnchantment: EnumCase({ type: CauldronEnchantmentIdentifier() }),
CeremonialItemSpecialAbility: EnumCase({ type: CeremonialItemSpecialAbilityIdentifier() }),
ChronicleEnchantment: EnumCase({ type: ChronicleEnchantmentIdentifier() }),
CombatSpecialAbility: EnumCase({ type: CombatSpecialAbilityIdentifier() }),
CombatStyleSpecialAbility: EnumCase({ type: CombatStyleSpecialAbilityIdentifier() }),
CommandSpecialAbility: EnumCase({ type: CommandSpecialAbilityIdentifier() }),
DaggerRitual: EnumCase({ type: DaggerRitualIdentifier() }),
FamiliarSpecialAbility: EnumCase({ type: FamiliarSpecialAbilityIdentifier() }),
FatePointSexSpecialAbility: EnumCase({ type: FatePointSexSpecialAbilityIdentifier() }),
FatePointSpecialAbility: EnumCase({ type: FatePointSpecialAbilityIdentifier() }),
FoolsHatEnchantment: EnumCase({ type: FoolsHatEnchantmentIdentifier() }),
GeneralSpecialAbility: EnumCase({ type: GeneralSpecialAbilityIdentifier() }),
InstrumentEnchantment: EnumCase({ type: InstrumentEnchantmentIdentifier() }),
KarmaSpecialAbility: EnumCase({ type: KarmaSpecialAbilityIdentifier() }),
Krallenkettenzauber: EnumCase({ type: KrallenkettenzauberIdentifier() }),
LiturgicalStyleSpecialAbility: EnumCase({ type: LiturgicalStyleSpecialAbilityIdentifier() }),
LycantropicGift: EnumCase({ type: LycantropicGiftIdentifier() }),
MagicalSign: EnumCase({ type: MagicalSignIdentifier() }),
MagicalSpecialAbility: EnumCase({ type: MagicalSpecialAbilityIdentifier() }),
MagicalTradition: EnumCase({ type: MagicalTraditionIdentifier() }),
MagicStyleSpecialAbility: EnumCase({ type: MagicStyleSpecialAbilityIdentifier() }),
OrbEnchantment: EnumCase({ type: OrbEnchantmentIdentifier() }),
PactGift: EnumCase({ type: PactGiftIdentifier() }),
ProtectiveWardingCircleSpecialAbility: EnumCase({
type: ProtectiveWardingCircleSpecialAbilityIdentifier(),
}),
RingEnchantment: EnumCase({ type: RingEnchantmentIdentifier() }),
Sermon: EnumCase({ type: SermonIdentifier() }),
SexSpecialAbility: EnumCase({ type: SexSpecialAbilityIdentifier() }),
SickleRitual: EnumCase({ type: SickleRitualIdentifier() }),
SikaryanDrainSpecialAbility: EnumCase({ type: SikaryanDrainSpecialAbilityIdentifier() }),
SkillStyleSpecialAbility: EnumCase({ type: SkillStyleSpecialAbilityIdentifier() }),
SpellSwordEnchantment: EnumCase({ type: SpellSwordEnchantmentIdentifier() }),
StaffEnchantment: EnumCase({ type: StaffEnchantmentIdentifier() }),
ToyEnchantment: EnumCase({ type: ToyEnchantmentIdentifier() }),
Trinkhornzauber: EnumCase({ type: TrinkhornzauberIdentifier() }),
VampiricGift: EnumCase({ type: VampiricGiftIdentifier() }),
Vision: EnumCase({ type: VisionIdentifier() }),
WandEnchantment: EnumCase({ type: WandEnchantmentIdentifier() }),
WeaponEnchantment: EnumCase({ type: WeaponEnchantmentIdentifier() }),
TradeSecret: EnumCase({ type: TradeSecretIdentifier() }),
}),
})
export const RequirableSelectOptionIdentifier = Enum(import.meta.url, {
name: "RequirableSelectOptionIdentifier",
values: () => ({
@@ -433,6 +493,9 @@ export const RequirableSelectOptionIdentifier = Enum(import.meta.url, {
Spell: EnumCase({ type: SpellIdentifier() }),
Ritual: EnumCase({ type: RitualIdentifier() }),
TradeSecret: EnumCase({ type: TradeSecretIdentifier() }),
ArcaneBardTradition: EnumCase({ type: ArcaneBardTraditionIdentifier() }),
ArcaneDancerTradition: EnumCase({ type: ArcaneDancerTraditionIdentifier() }),
Element: EnumCase({ type: ElementIdentifier() }),
}),
})
@@ -470,6 +533,7 @@ export const EquipmentIdentifier = Enum(import.meta.url, {
LuxuryGood: EnumCase({ type: LuxuryGoodIdentifier() }),
MagicalArtifact: EnumCase({ type: MagicalArtifactIdentifier() }),
MusicalInstrument: EnumCase({ type: MusicalInstrumentIdentifier() }),
Newspaper: EnumCase({ type: NewspaperIdentifier() }),
OrienteeringAid: EnumCase({ type: OrienteeringAidIdentifier() }),
Poison: EnumCase({ type: PoisonIdentifier() }),
RopeOrChain: EnumCase({ type: RopeOrChainIdentifier() }),
+4 -4
View File
@@ -9,7 +9,7 @@ import {
TypeAlias,
} from "tsondb/schema/def"
import { InfluencePrerequisites } from "./_Prerequisite.js"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { Errata } from "./source/_Erratum.js"
import { src } from "./source/_PublicationRef.js"
@@ -22,9 +22,9 @@ export const Influence = Entity(import.meta.url, {
type: IncludeIdentifier(InfluencePrerequisites),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"InfluenceTranslation",
"Influence",
Object({
name: Required({
comment: "The influences name.",
@@ -38,7 +38,7 @@ export const Influence = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+72
View File
@@ -0,0 +1,72 @@
import {
Array,
EnumCase,
GenEnum,
GenIncludeIdentifier,
Param,
TypeArgument,
type Enum,
type IncludeIdentifier,
type TypeParameter,
} from "tsondb/schema/def"
type BinaryMathOperationName =
| "Addition"
| "Subtraction"
| "Multiplication"
| "Division"
| "Exponentiation"
type BinaryMathOperation = {
[K in BinaryMathOperationName]: EnumCase<
Array<IncludeIdentifier<[Value: TypeParameter], MathOperation>>
>
}
export type MathOperation = Enum<
"MathOperation",
{
Value: EnumCase<TypeArgument>
} & BinaryMathOperation,
[Value: TypeParameter]
>
export const MathOperation: MathOperation = GenEnum(import.meta.url, {
name: "MathOperation",
parameters: [Param("Value")],
values: Value => {
const binaryOperationType = Array(
GenIncludeIdentifier<MathOperation, [Value: TypeParameter]>(MathOperation, [
TypeArgument(Value),
]),
{ minItems: 2, maxItems: 2 },
)
return {
Value: EnumCase({
comment: "A direct value.",
type: TypeArgument(Value),
}),
Addition: EnumCase({
comment: "Adds two values.",
type: binaryOperationType,
}),
Subtraction: EnumCase({
comment: "Subtracts the right value from the left value.",
type: binaryOperationType,
}),
Multiplication: EnumCase({
comment: "Multiplies two values.",
type: binaryOperationType,
}),
Division: EnumCase({
comment: "Divides the left value by the right value.",
type: binaryOperationType,
}),
Exponentiation: EnumCase({
comment: "Raises the left value to the power of the right value.",
type: binaryOperationType,
}),
}
},
})
+16 -4
View File
@@ -18,7 +18,7 @@ import {
TypeAlias,
TypeArgument,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "./Locale.js"
import { NestedTranslationMap } from "./Locale.js"
import { DisplayOption } from "./prerequisites/DisplayOption.js"
import {
AdvantageDisadvantagePrerequisiteGroup,
@@ -34,7 +34,9 @@ import {
PersonalityTraitPrerequisiteGroup,
ProfessionPrerequisiteGroup,
PublicationPrerequisiteGroup,
RulePrerequisiteGroup,
SpellworkPrerequisiteGroup,
TribePrerequisiteGroup,
} from "./prerequisites/PrerequisiteGroups.js"
const PrerequisiteGroup = GenTypeAlias(import.meta.url, {
@@ -47,14 +49,14 @@ const PrerequisiteGroup = GenTypeAlias(import.meta.url, {
list: Required({
type: Array(TypeArgument(T), { minItems: 2 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PrerequisiteGroupTranslation",
"PrerequisiteGroup",
Object({
text: Required({
type: String({ minLength: 1 }),
}),
})
}),
),
}),
})
@@ -118,6 +120,11 @@ const PrerequisitesForLevels = GenTypeAlias(import.meta.url, {
}),
})
export const RulePrerequisites = TypeAlias(import.meta.url, {
name: "RulePrerequisites",
type: () => GenIncludeIdentifier(PlainPrerequisites, [IncludeIdentifier(RulePrerequisiteGroup)]),
})
export const DerivedCharacteristicPrerequisites = TypeAlias(import.meta.url, {
name: "DerivedCharacteristicPrerequisites",
type: () =>
@@ -202,6 +209,11 @@ export const AnimistPowerPrerequisites = TypeAlias(import.meta.url, {
GenIncludeIdentifier(PlainPrerequisites, [IncludeIdentifier(AnimistPowerPrerequisiteGroup)]),
})
export const TribePrerequisites = TypeAlias(import.meta.url, {
name: "TribePrerequisites",
type: () => GenIncludeIdentifier(PlainPrerequisites, [IncludeIdentifier(TribePrerequisiteGroup)]),
})
export const GeodeRitualPrerequisites = TypeAlias(import.meta.url, {
name: "GeodeRitualPrerequisites",
type: () =>
+35
View File
@@ -0,0 +1,35 @@
import {
Enum,
EnumCase,
GenTypeAlias,
Object,
Param,
Required,
TypeArgument,
} from "tsondb/schema/def"
export const SizeCategory = Enum(import.meta.url, {
name: "SizeCategory",
comment: "Each creature fits into one of these size categories.",
values: () => ({
Tiny: EnumCase({ type: null }),
Small: EnumCase({ type: null }),
Medium: EnumCase({ type: null }),
Large: EnumCase({ type: null }),
Huge: EnumCase({ type: null }),
}),
})
export const BySizeCategory = GenTypeAlias(import.meta.url, {
name: "BySizeCategory",
comment: "An object that holds values for the different size categories",
parameters: [Param("T")],
type: T =>
Object({
tiny: Required({ type: TypeArgument(T) }),
small: Required({ type: TypeArgument(T) }),
medium: Required({ type: TypeArgument(T) }),
large: Required({ type: TypeArgument(T) }),
huge: Required({ type: TypeArgument(T) }),
}),
})
+4 -4
View File
@@ -10,7 +10,7 @@ import {
TypeAlias,
} from "tsondb/schema/def"
import { EquipmentIdentifier } from "../_IdentifierGroup.js"
import { NestedLocaleMap } from "../Locale.js"
import { NestedTranslationMap } from "../Locale.js"
import { src } from "../source/_PublicationRef.js"
export const EquipmentPackage = Entity(import.meta.url, {
@@ -24,15 +24,15 @@ export const EquipmentPackage = Entity(import.meta.url, {
type: Array(IncludeIdentifier(EquipmentPackageItem), { minItems: 2 }),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"EquipmentPackageTranslation",
"EquipmentPackage",
Object({
name: Required({
comment: "The equipment packages name.",
type: String({ minLength: 1 }),
}),
})
}),
),
}),
displayName: {},
+4 -4
View File
@@ -1,5 +1,5 @@
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Cost } from "./_Item.js"
@@ -14,9 +14,9 @@ export const Ammunition = Entity(import.meta.url, {
type: IncludeIdentifier(Cost),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AmmunitionTranslation",
"Ammunition",
Object({
name: Required({
comment: "The items name.",
@@ -37,7 +37,7 @@ export const Ammunition = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+31 -10
View File
@@ -13,10 +13,10 @@ import {
TypeAlias,
} from "tsondb/schema/def"
import { ArmorIdentifier, ArmorTypeIdentifier } from "../../_Identifier.js"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Complexity, Cost, Weight } from "./_Item.js"
import { ComplexComplexity, Cost, RestrictedTo, Weight } from "./_Item.js"
export const Armor = Entity(import.meta.url, {
name: "Armor",
@@ -33,7 +33,7 @@ export const Armor = Entity(import.meta.url, {
}),
complexity: Optional({
comment: "The complexity of crafting the item.",
type: IncludeIdentifier(Complexity),
type: IncludeIdentifier(ArmorComplexity),
}),
protection: Required({
comment: "The PRO value.",
@@ -55,10 +55,15 @@ export const Armor = Entity(import.meta.url, {
comment: "Specify if armor is only available for a specific hit zone.",
type: IncludeIdentifier(HitZone),
}),
restrictedTo: Optional({
comment:
"Define if during character creation this weapon can only be bought by a specific subset of characters.",
type: IncludeIdentifier(RestrictedTo),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ArmorTranslation",
"Armor",
Object({
name: Required({
comment: "The items name.",
@@ -87,7 +92,7 @@ export const Armor = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -117,9 +122,14 @@ export const SecondaryArmor = TypeAlias(import.meta.url, {
comment: "Specify if armor is only available for a specific hit zone.",
type: IncludeIdentifier(HitZone),
}),
translations: NestedLocaleMap(
restrictedTo: Optional({
comment:
"Define if during character creation this armor can only be bought by a specific subset of characters.",
type: IncludeIdentifier(RestrictedTo),
}),
translations: NestedTranslationMap(
Optional,
"SecondaryArmorTranslation",
"SecondaryArmor",
Object(
{
advantage: Optional({
@@ -131,12 +141,23 @@ export const SecondaryArmor = TypeAlias(import.meta.url, {
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
const ArmorComplexity = Enum(import.meta.url, {
name: "ArmorComplexity",
comment: "The complexity of crafting the armor.",
values: () => ({
Primitive: EnumCase({ type: null }),
Simple: EnumCase({ type: null }),
Complex: EnumCase({ type: IncludeIdentifier(ComplexComplexity) }),
Various: EnumCase({ type: null }),
}),
})
export const Protection = TypeAlias(import.meta.url, {
name: "Protection",
comment: "The PRO value.",
+156 -6
View File
@@ -1,5 +1,17 @@
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "../../Locale.js"
import {
Array,
Entity,
Enum,
EnumCase,
IncludeIdentifier,
Object,
Optional,
Required,
String,
TypeAlias,
} from "tsondb/schema/def"
import { SkillIdentifier } from "../../_Identifier.js"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Complexity, Cost, StructurePoints, Weight } from "./_Item.js"
@@ -9,9 +21,13 @@ export const Book = Entity(import.meta.url, {
namePlural: "Books",
type: () =>
Object({
types: Required({
comment: "The type of book.",
type: Array(IncludeIdentifier(BookType), { minItems: 1, uniqueItems: true }),
}),
cost: Required({
comment: "The cost in silverthalers.",
type: IncludeIdentifier(Cost),
type: IncludeIdentifier(BookCost),
}),
weight: Optional({
comment: "The weight in kg.",
@@ -27,9 +43,9 @@ export const Book = Entity(import.meta.url, {
type: IncludeIdentifier(StructurePoints),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"BookTranslation",
"Book",
Object({
name: Required({
comment: "The items name.",
@@ -58,8 +74,142 @@ export const Book = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
})
const BookType = Enum(import.meta.url, {
name: "BookType",
comment: "The type of book.",
values: () => ({
Mundane: EnumCase({
comment: "A mundane book without special rules.",
type: IncludeIdentifier(MundaneBookType),
}),
Magical: EnumCase({
comment: "A magical book.",
type: null,
}),
Religious: EnumCase({
comment: "A religious book.",
type: null,
}),
}),
})
const MundaneBookType = Enum(import.meta.url, {
name: "MundaneBookType",
comment: "The type of mundane book, i.e. the type of writing it contains.",
values: () => ({
RomanceNovel: EnumCase({
comment: "A romance novel.",
type: null,
}),
Poetry: EnumCase({
comment: "A piece of poetry.",
type: null,
}),
PoliticalPamphlet: EnumCase({
comment: "A political pamphlet.",
type: null,
}),
CrimeStory: EnumCase({
comment: "A crime story.",
type: null,
}),
FairyTale: EnumCase({
comment: "A fairy tale.",
type: null,
}),
Novel: EnumCase({
comment: "A novel.",
type: null,
}),
ProfessionalPublication: EnumCase({
comment:
"A professional publication about a specific topic, represented by a (knowledge) skill.",
type: SkillIdentifier(),
}),
}),
})
const BookCost = Enum(import.meta.url, {
name: "BookCost",
comment:
"The cost of the book, which may be a single value or multiple values that are defined by specific editions or other defining factors of the book.",
values: () => ({
Single: EnumCase({
comment: "The book only has a single cost description.",
type: IncludeIdentifier(BookCostVariant),
}),
Multiple: EnumCase({
comment: "The books cost varies by edition or other factors.",
type: Array(IncludeIdentifier(BookCostVariant), { minItems: 2 }),
}),
}),
})
const BookCostVariant = Enum(import.meta.url, {
name: "BookCostVariant",
comment: "A variant for the books cost.",
values: () => ({
Definite: EnumCase({
comment: "A definite cost value.",
type: IncludeIdentifier(DefiniteBookCostVariant),
}),
Indefinite: EnumCase({
comment: "An indefinite cost value.",
type: IncludeIdentifier(IndefiniteBookCostVariant),
}),
}),
})
const DefiniteBookCostVariant = TypeAlias(import.meta.url, {
name: "DefiniteBookCostVariant",
comment: "A definite cost value for the book, such as a specific cost or cost range.",
type: () =>
Object({
cost: Required({
comment: "The cost in silverthalers.",
type: IncludeIdentifier(Cost),
}),
translations: NestedTranslationMap(
Optional,
"DefiniteBookCostVariant",
Object(
{
label: Optional({
comment: "The label for the cost variant, e.g. “Original script”.",
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 },
),
),
}),
})
const IndefiniteBookCostVariant = TypeAlias(import.meta.url, {
name: "IndefiniteBookCostVariant",
comment:
"An indefinite cost value for the book, which means it is impossible to narrow the range of the cost value by any numbers.",
type: () =>
Object({
translations: NestedTranslationMap(
Required,
"IndefiniteBookCostVariant",
Object({
label: Optional({
comment: "The label for the cost variant, e.g. “Original script”.",
type: String({ minLength: 1, isMarkdown: true }),
}),
description: Required({
comment: "The description of the cost variant, e.g. “less”.",
type: String({ minLength: 1, isMarkdown: true }),
}),
}),
),
}),
})
+4 -4
View File
@@ -8,7 +8,7 @@ import {
Required,
String,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { AlternativeName } from "../../_AlternativeNames.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
@@ -40,9 +40,9 @@ export const Elixir = Entity(import.meta.url, {
type: IncludeIdentifier(RecipeTradeSecret),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"ElixirTranslationTranslation",
"ElixirTranslation",
Object({
name: Required({
comment: "The items name.",
@@ -76,7 +76,7 @@ export const Elixir = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -1,7 +1,6 @@
import { Array, Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { BlessedTraditionIdentifier } from "../../_Identifier.js"
import { Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { src } from "../../source/_PublicationRef.js"
import { CombatUse, Cost, DefaultItemTranslations, StructurePoints } from "./_Item.js"
import { CombatUse, Cost, DefaultItemTranslations, RestrictedTo, StructurePoints } from "./_Item.js"
export const EquipmentOfBlessedOnes = Entity(import.meta.url, {
name: "EquipmentOfBlessedOnes",
@@ -17,9 +16,10 @@ export const EquipmentOfBlessedOnes = Entity(import.meta.url, {
"The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.",
type: IncludeIdentifier(StructurePoints),
}),
associated_tradition: Required({
comment: "The deity/deities associated with the equipment item.",
type: Array(BlessedTraditionIdentifier(), { minItems: 1, uniqueItems: true }),
restrictedTo: Optional({
comment:
"Define if during character creation this item can only be bought by a specific subset of characters.",
type: IncludeIdentifier(RestrictedTo),
}),
combat_use: Optional({
comment:
@@ -1,5 +1,5 @@
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Cost } from "./_Item.js"
@@ -14,9 +14,9 @@ export const GemOrPreciousStone = Entity(import.meta.url, {
type: IncludeIdentifier(Cost),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"GemOrPreciousStoneTranslation",
"GemOrPreciousStone",
Object({
name: Required({
comment: "The items name.",
@@ -41,7 +41,7 @@ export const GemOrPreciousStone = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
+55 -3
View File
@@ -1,6 +1,8 @@
import { Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { Entity, IncludeIdentifier, Object, Optional, Required, String } from "tsondb/schema/def"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Cost, DefaultItemTranslations, Weight } from "./_Item.js"
import { CombatUse, Complexity, Cost, StructurePoints, Weight } from "./_Item.js"
export const MagicalArtifact = Entity(import.meta.url, {
name: "MagicalArtifact",
@@ -15,8 +17,58 @@ export const MagicalArtifact = Entity(import.meta.url, {
comment: "The weight in kg.",
type: IncludeIdentifier(Weight),
}),
complexity: Optional({
comment: "The complexity of crafting the item.",
type: IncludeIdentifier(Complexity),
}),
structure_points: Optional({
comment:
"The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.",
type: IncludeIdentifier(StructurePoints),
}),
combat_use: Optional({
comment:
"The item can also be used either as an improvised weapon or as an armor, although this is not the primary use case of the item.",
type: IncludeIdentifier(CombatUse),
}),
src,
translations: DefaultItemTranslations("MagicalArtifact"),
translations: NestedTranslationMap(
Required,
`MagicalArtifact`,
Object({
name: Required({
comment: "The items name.",
type: String({ minLength: 1 }),
}),
secondary_name: Optional({
comment: "An auxiliary name or label of the item, if available.",
type: String({ minLength: 1 }),
}),
description: Optional({
comment: "Description text.",
type: String({ minLength: 1, isMarkdown: true }),
}),
note: Optional({
comment: "A note.",
type: String({ minLength: 1, isMarkdown: true }),
}),
rules: Optional({
comment: "Rules text.",
type: String({ minLength: 1, isMarkdown: true }),
}),
effect: Optional({
comment: "Effect text.",
type: String({ minLength: 1, isMarkdown: true }),
}),
cost: Optional({
comment: "Cost text.",
type: String({ minLength: 1, isMarkdown: true }),
}),
errata: Optional({
type: IncludeIdentifier(Errata),
}),
}),
),
}),
displayName: {},
})
+13 -1
View File
@@ -1,6 +1,13 @@
import { Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { src } from "../../source/_PublicationRef.js"
import { CombatUse, Complexity, Cost, DefaultItemTranslations, Weight } from "./_Item.js"
import {
CombatUse,
Complexity,
Cost,
DefaultItemTranslations,
StructurePoints,
Weight,
} from "./_Item.js"
export const MusicalInstrument = Entity(import.meta.url, {
name: "MusicalInstrument",
@@ -19,6 +26,11 @@ export const MusicalInstrument = Entity(import.meta.url, {
comment: "The complexity of crafting the item.",
type: IncludeIdentifier(Complexity),
}),
structure_points: Optional({
comment:
"The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.",
type: IncludeIdentifier(StructurePoints),
}),
combat_use: Optional({
comment:
"The item can also be used either as an improvised weapon or as an armor, although this is not the primary use case of the item.",
+36
View File
@@ -0,0 +1,36 @@
import { Array, Entity, IncludeIdentifier, Object, Required, String } from "tsondb/schema/def"
import { NestedTranslationMap } from "../../Locale.js"
import { src } from "../../source/_PublicationRef.js"
import { Cost } from "./_Item.js"
export const Newspaper = Entity(import.meta.url, {
name: "Newspaper",
namePlural: "Newspapers",
type: () =>
Object({
cost: Required({
comment: "The cost in silverthalers.",
type: IncludeIdentifier(Cost),
}),
src,
translations: NestedTranslationMap(
Required,
"Newspaper",
Object({
name: Required({
comment: "The newspapers name.",
type: String({ minLength: 1 }),
}),
topics: Required({
comment: "The topics covered by the newspaper.",
type: Array(String({ minLength: 1 }), { minItems: 1, uniqueItems: true }),
}),
placeOfPublication: Required({
comment: "The place the newspaper is published.",
type: String({ minLength: 1 }),
}),
}),
),
}),
displayName: {},
})
+97 -25
View File
@@ -15,10 +15,11 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { AlternativeName } from "../../_AlternativeNames.js"
import { Dice } from "../../_Dice.js"
import { Reduceable, Resistance } from "../../_DiseasePoison.js"
import { MathOperation } from "../../_MathExpression.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { EffectType, LaboratoryLevel, RecipeTradeSecret } from "./_Herbary.js"
@@ -57,9 +58,9 @@ export const Poison = Entity(import.meta.url, {
type: IncludeIdentifier(PoisonCost),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"PoisonTranslation",
"Poison",
Object({
name: Required({
comment: "The name of the poison.",
@@ -80,7 +81,7 @@ export const Poison = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -112,6 +113,7 @@ const PoisonDuration = Enum(import.meta.url, {
Instant: EnumCase({ type: null }),
Constant: EnumCase({ type: IncludeIdentifier(ConstantPoisonTime) }),
DiceBased: EnumCase({ type: IncludeIdentifier(DiceBasedPoisonTime) }),
ExpressionBased: EnumCase({ type: IncludeIdentifier(ExpressionBasedPoisonTime) }),
Indefinite: EnumCase({ type: IncludeIdentifier(IndefinitePoisonTime) }),
}),
})
@@ -130,15 +132,15 @@ export const NoPoisonCost = TypeAlias(import.meta.url, {
name: "NoPoisonCost",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"NoPoisonCostTranslation",
"NoPoisonCost",
Object({
description: Required({
comment: "A description of the cost.",
type: String({ minLength: 1, isMarkdown: true }),
}),
})
}),
),
}),
})
@@ -147,15 +149,15 @@ export const IndefinitePoisonCost = TypeAlias(import.meta.url, {
name: "IndefinitePoisonCost",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IndefinitePoisonCostTranslation",
"IndefinitePoisonCost",
Object({
description: Required({
comment: "A description of the cost.",
type: String({ minLength: 1, isMarkdown: true }),
}),
})
}),
),
}),
})
@@ -190,6 +192,35 @@ export const DiceBasedPoisonTime = TypeAlias(import.meta.url, {
}),
})
export const ExpressionBasedPoisonTime = TypeAlias(import.meta.url, {
name: "ExpressionBasedPoisonTime",
type: () =>
Object({
value: Required({
type: IncludeIdentifier(ExpressionBasedPoisonTimeValue),
}),
unit: Required({
type: IncludeIdentifier(PoisonTimeUnit),
}),
}),
})
export const ExpressionBasedPoisonTimeValue = TypeAlias(import.meta.url, {
name: "ExpressionBasedPoisonTimeValue",
type: () =>
GenIncludeIdentifier(MathOperation, [
IncludeIdentifier(ExpressionBasedPoisonTimeExpressionValue),
]),
})
export const ExpressionBasedPoisonTimeExpressionValue = Enum(import.meta.url, {
name: "ExpressionBasedPoisonTimeExpressionValue",
values: () => ({
Constant: EnumCase({ type: Integer({ minimum: 1 }) }),
Dice: EnumCase({ type: IncludeIdentifier(Dice) }),
}),
})
const PoisonTimeUnit = Enum(import.meta.url, {
name: "PoisonTimeUnit",
values: () => ({
@@ -204,9 +235,9 @@ export const IndefinitePoisonTime = TypeAlias(import.meta.url, {
name: "IndefinitePoisonTime",
type: () =>
Object({
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"IndefinitePoisonTimeTranslation",
"IndefinitePoisonTime",
Object(
{
description: Optional({
@@ -214,8 +245,8 @@ export const IndefinitePoisonTime = TypeAlias(import.meta.url, {
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
@@ -243,6 +274,10 @@ export const AnimalVenom = TypeAlias(import.meta.url, {
comment: "If `false`, the poison cannot be extracted.",
type: Boolean(),
}),
trade_secret: Optional({
comment: "AP value and prerequisites of the poisons trade secret.",
type: IncludeIdentifier(RecipeTradeSecret),
}),
}),
})
@@ -251,9 +286,35 @@ const AnimalVenomLevel = Enum(import.meta.url, {
values: () => ({
QualityLevel: EnumCase({ type: null }),
Constant: EnumCase({ type: Integer({ minimum: 1, maximum: 6 }) }),
BySubtype: EnumCase({ type: IncludeIdentifier(AnimalVenomLevelBySubType) }),
}),
})
const AnimalVenomLevelBySubType = TypeAlias(import.meta.url, {
name: "AnimalVenomLevelBySubType",
type: () =>
Array(
Object({
value: Required({
comment: "The poisons level.",
type: Integer({ minimum: 1, maximum: 6 }),
}),
translations: NestedTranslationMap(
Required,
"AnimalVenomLevelBySubType",
Object({
name: Required({
comment:
"The subtype name. If there are multiple subtypes with the same level, specify them separately.",
type: String({ minLength: 1 }),
}),
}),
),
}),
{ minItems: 1 },
),
})
export const AlchemicalPoison = TypeAlias(import.meta.url, {
name: "AlchemicalPoison",
type: () =>
@@ -282,9 +343,9 @@ export const AlchemicalPoison = TypeAlias(import.meta.url, {
comment: "Additional information if the poison is an intoxicant.",
type: IncludeIdentifier(Intoxicant),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"AlchemicalPoisonTranslation",
"AlchemicalPoison",
Object({
typical_ingredients: Required({
comment: "A list of typical ingredients.",
@@ -294,7 +355,7 @@ export const AlchemicalPoison = TypeAlias(import.meta.url, {
comment: "Prerequsites for the brewing process, if any.",
type: String({ minLength: 1, isMarkdown: true }),
}),
})
}),
),
}),
})
@@ -337,9 +398,9 @@ export const DemonicPoison = TypeAlias(import.meta.url, {
comment: "The poisons level.",
type: IncludeIdentifier(DemonicPoisonLevel),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"DemonicPoisonTranslation",
"DemonicPoison",
Object(
{
note: Optional({
@@ -347,8 +408,8 @@ export const DemonicPoison = TypeAlias(import.meta.url, {
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
@@ -403,9 +464,9 @@ export const Intoxicant = TypeAlias(import.meta.url, {
"The chance of getting addicted after an ingestion in addition to the maximum interval at which it, while addicted, must be ingested to not suffer from withdrawal symptoms.",
type: IncludeIdentifier(IntoxicantAddiction),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"IntoxicantTranslation",
"Intoxicant",
Object({
ingestion: Required({
comment: "How to ingest the intoxicant.",
@@ -416,7 +477,7 @@ export const Intoxicant = TypeAlias(import.meta.url, {
"The intoxicants side effects that always happen, no matter whether the intoxicant has the default or the reduced effect.",
type: String({ minLength: 1, isMarkdown: true }),
}),
overdose: Required({
overdose: Optional({
comment:
"What happens if the intoxicant has been overdosed, that is, it has been ingested another time within the duration.",
type: String({ minLength: 1, isMarkdown: true }),
@@ -425,7 +486,7 @@ export const Intoxicant = TypeAlias(import.meta.url, {
comment: "Special information about the intoxicant.",
type: String({ minLength: 1, isMarkdown: true }),
}),
})
}),
),
}),
})
@@ -458,6 +519,17 @@ export const IntoxicantAddiction = TypeAlias(import.meta.url, {
"The maximum interval at which it, while addicted, must be ingested to not suffer from withdrawal symptoms.",
type: IncludeIdentifier(IntoxicantAddictionInterval),
}),
translations: NestedTranslationMap(
Optional,
"IntoxicantAddiction",
Object({
chance: Required({
comment:
"The chance of getting addicted after an ingestion. Use if and only if the plain constant percent is not sufficient.",
type: String({ minLength: 1, isMarkdown: true }),
}),
}),
),
}),
})
+10 -2
View File
@@ -1,6 +1,6 @@
import { Entity, IncludeIdentifier, Object, Required } from "tsondb/schema/def"
import { Entity, IncludeIdentifier, Object, Optional, Required } from "tsondb/schema/def"
import { src } from "../../source/_PublicationRef.js"
import { Cost, DefaultItemTranslations } from "./_Item.js"
import { Complexity, Cost, DefaultItemTranslations, Weight } from "./_Item.js"
export const Vehicle = Entity(import.meta.url, {
name: "Vehicle",
@@ -11,6 +11,14 @@ export const Vehicle = Entity(import.meta.url, {
comment: "The cost in silverthalers.",
type: IncludeIdentifier(Cost),
}),
weight: Optional({
comment: "The weight in kg.",
type: IncludeIdentifier(Weight),
}),
complexity: Optional({
comment: "The complexity of crafting the item.",
type: IncludeIdentifier(Complexity),
}),
src,
translations: DefaultItemTranslations("Vehicle"),
}),
+19 -68
View File
@@ -1,8 +1,5 @@
import {
Array,
Entity,
Enum,
EnumCase,
IncludeIdentifier,
NestedEntityMap,
Object,
@@ -11,17 +8,11 @@ import {
String,
TypeAlias,
} from "tsondb/schema/def"
import {
BlessedTraditionIdentifier,
CultureIdentifier,
MagicalTraditionIdentifier,
RaceIdentifier,
} from "../../_Identifier.js"
import { CloseCombatTechnique, RangedCombatTechnique } from "../../CombatTechnique.js"
import { NestedLocaleMap } from "../../Locale.js"
import { NestedTranslationMap } from "../../Locale.js"
import { Errata } from "../../source/_Erratum.js"
import { src } from "../../source/_PublicationRef.js"
import { Complexity, Cost, StructurePoints, Weight } from "./_Item.js"
import { Complexity, Cost, RestrictedTo, StructurePoints, Weight } from "./_Item.js"
import { MeleeWeapon } from "./_MeleeWeapon.js"
import { RangedWeapon } from "./_RangedWeapon.js"
@@ -52,6 +43,7 @@ export const Weapon = Entity(import.meta.url, {
"A list of stat blocks for each close combat technique this weapon can be used with.",
type: NestedEntityMap({
name: "MeleeWeaponUse",
namePlural: "MeleeWeaponUses",
secondaryEntity: CloseCombatTechnique,
type: IncludeIdentifier(MeleeWeapon),
}),
@@ -61,28 +53,20 @@ export const Weapon = Entity(import.meta.url, {
"A list of stat blocks for each ranged combat technique this weapon can be used with.",
type: NestedEntityMap({
name: "RangedWeaponUse",
namePlural: "RangedWeaponUses",
secondaryEntity: RangedCombatTechnique,
type: IncludeIdentifier(RangedWeapon),
}),
}),
sanctified_by: Optional({
comment: "If the weapon is sanctified by a god and thus restricted to it's Blessed Ones.",
type: IncludeIdentifier(SanctifiedBy),
}),
restricted_to_cultures: Optional({
restrictedTo: Optional({
comment:
"Define if during character creation this weapon can only be bought by characters of a specific race or culture.",
type: IncludeIdentifier(RestrictedToCultures),
}),
restricted_to_traditions: Optional({
comment:
"Define if during character creation this weapon can only be bought by characters of specific magical or blessed traditions.",
type: Array(MagicalTraditionIdentifier(), { minItems: 1 }),
"Define if during character creation this weapon can only be bought by a specific subset of characters.",
type: IncludeIdentifier(RestrictedTo),
}),
src,
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Required,
"WeaponTranslation",
"Weapon",
Object({
name: Required({
comment: "The items name.",
@@ -111,7 +95,7 @@ export const Weapon = Entity(import.meta.url, {
errata: Optional({
type: IncludeIdentifier(Errata),
}),
})
}),
),
}),
displayName: {},
@@ -126,6 +110,7 @@ export const ImprovisedWeapon = TypeAlias(import.meta.url, {
"A list of stat blocks for each close combat technique this weapon can be used with.",
type: NestedEntityMap({
name: "ImprovisedMeleeWeaponUse",
namePlural: "ImprovisedMeleeWeaponUses",
secondaryEntity: CloseCombatTechnique,
type: IncludeIdentifier(MeleeWeapon),
}),
@@ -135,27 +120,19 @@ export const ImprovisedWeapon = TypeAlias(import.meta.url, {
"A list of stat blocks for each ranged combat technique this weapon can be used with.",
type: NestedEntityMap({
name: "ImprovisedRangedWeaponUse",
namePlural: "ImprovisedRangedWeaponUses",
secondaryEntity: RangedCombatTechnique,
type: IncludeIdentifier(RangedWeapon),
}),
}),
sanctified_by: Optional({
comment: "If the weapon is sanctified by a god and thus restricted to it's Blessed Ones.",
type: IncludeIdentifier(SanctifiedBy),
}),
restricted_to_cultures: Optional({
restrictedTo: Optional({
comment:
"Define if during character creation this weapon can only be bought by characters of a specific race or culture.",
type: IncludeIdentifier(RestrictedToCultures),
"Define if during character creation this weapon can only be bought by a specific subset of characters.",
type: IncludeIdentifier(RestrictedTo),
}),
restricted_to_traditions: Optional({
comment:
"Define if during character creation this weapon can only be bought by characters of specific magical or blessed traditions.",
type: Array(MagicalTraditionIdentifier(), { minItems: 1 }),
}),
translations: NestedLocaleMap(
translations: NestedTranslationMap(
Optional,
"ImprovisedWeaponTranslation",
"ImprovisedWeapon",
Object(
{
advantage: Optional({
@@ -167,34 +144,8 @@ export const ImprovisedWeapon = TypeAlias(import.meta.url, {
type: String({ minLength: 1, isMarkdown: true }),
}),
},
{ minProperties: 1 }
)
{ minProperties: 1 },
),
),
}),
})
export const SanctifiedBy = TypeAlias(import.meta.url, {
name: "SanctifiedBy",
comment: "If the weapon is sanctified by a god and thus restricted to it's Blessed Ones.",
type: () => Array(BlessedTraditionIdentifier(), { minItems: 1 }),
})
const RestrictedToCultures = Enum(import.meta.url, {
name: "RestrictedToCultures",
comment:
"Define if during character creation this weapon can only be bought by characters of a specific race or culture.",
values: () => ({
CulturesOfRace: EnumCase({ type: RaceIdentifier() }),
Cultures: EnumCase({ type: IncludeIdentifier(RestrictedToSpecificCultures) }),
}),
})
const RestrictedToSpecificCultures = TypeAlias(import.meta.url, {
name: "RestrictedToSpecificCultures",
type: () =>
Object({
list: Required({
type: Array(CultureIdentifier(), { minItems: 1 }),
}),
}),
})

Some files were not shown because too many files have changed in this diff Show More