Compare commits

...
591 Commits
Author SHA1 Message Date
Lector 9718f7d341 feat!: derived select options based on property
Refs: #175
Closes #174
2026-06-27 14:55:52 +02:00
Lector 0c8f3518ea feat!: active usage type for combat styles
Refs: #171
Closes #170
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-27 14:06:46 +02:00
Lukas Obermann 3b3ae6f3a6 feat: optional additional name for magic styles 2026-06-24 22:19:55 +02:00
Lukas Obermann 028e438909 fix!: remove bannzeichen option native name 2026-06-21 22:53:25 +02:00
Lukas Obermann 04f2a32a5e build(release): 0.53.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-21 21:46:25 +02:00
Lukas Obermann 0e25dbf4a7 feat!: add bannzeichen cost map 2026-06-21 21:46:15 +02:00
Lukas Obermann 7e977925b5 feat: separate tradition artifact enhancement identifiers
Still includes them in SpecialAbilityIdentifier group, but now have
their own group as well.
2026-06-21 21:28:44 +02:00
Lukas Obermann 11cadd76ba build(release): 0.52.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-21 14:28:11 +02:00
Lukas Obermann 249af59f6a fix: combined unique constraint for botanic regions 2026-06-21 14:28:05 +02:00
Lukas Obermann bb7d59e8b7 build(release): 0.52.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-20 08:54:41 +02:00
Lector 1e30f55407 feat: optional translation for combat technique restrictions
Refs: #169
Closes #168
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-20 08:54:20 +02:00
Lukas Obermann b70343ba6e build(release): 0.52.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-19 16:49:38 +02:00
Lector 3b522aa9d7 feat: botanic region uniqueness per parent
Refs: #166
Closes #165
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-19 16:43:50 +02:00
Lector 894fe21536 feat!: enchantment cost map
Refs: #167
Closes #164
2026-06-19 16:40:55 +02:00
Lukas Obermann e89c4390be build(release): 0.51.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-18 20:44:19 +02:00
Lector da1d2459db feat: add schemas for goblin tradition artifact enchantments
Refs: #163
Closes #162
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-18 20:43:41 +02:00
Lector 6c62616285 feat!: rework plants schema
Refs: #161
Closes #160
2026-06-18 14:52:54 +02:00
Lukas Obermann 6c6d7a07fd build(release): 0.50.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-14 17:56:01 +02:00
Lukas Obermann 9d97dd4ec9 feat: primary blessing for tradition
This is used to complete each blessing’s tradition list.
2026-06-14 17:55:53 +02:00
Lector 27236a7588 feat: trade secrets for herbal aids
Refs: #159
Closes #158
2026-06-14 17:25:19 +02:00
Lukas Obermann 959a5f1038 build(release): 0.50.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-14 14:14:05 +02:00
Lukas Obermann 3a3af6e574 feat: allow arbitrary text input for enhancements 2026-06-14 14:13:55 +02:00
Lukas Obermann 1d06407eb4 feat!: allow arbitrary AP values for enhancements 2026-06-14 14:04:37 +02:00
Lukas Obermann 89c5a10597 refactor: remove unused check-result based types 2026-06-14 12:21:28 +02:00
Lukas Obermann f6bb84fcb6 build(release): 0.49.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-14 00:12:31 +02:00
Lukas Obermann 64ee1cce6a fix!: add missing skill rating case to parameter expression 2026-06-14 00:12:28 +02:00
Lukas Obermann 500a2631b9 build(release): 0.48.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-13 23:29:23 +02:00
Lukas Obermann 31798ca33f feat!: unify some skill durations and ranges with expressions 2026-06-13 23:29:04 +02:00
Lukas Obermann 296962d3ce build(release): 0.47.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-13 22:18:06 +02:00
Lukas Obermann b5ece95103 feat: culture language translations 2026-06-13 22:18:00 +02:00
Lukas Obermann 88da9b1528 build(release): 0.47.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-13 13:47:04 +02:00
Lukas Obermann cdb86e9aa6 feat: optional sources for skill applications
This can be used to remove an application in a later revision of a book.
2026-06-13 13:46:50 +02:00
Lukas Obermann b9e4fff14c refactor: unify tradition artifact enchantment translations
This also makes cost_note available to all enchantment entities.
2026-06-13 13:35:39 +02:00
Lukas Obermann 6c9f7a6ec2 fix: profession cantrips must be unique 2026-06-09 09:57:11 +02:00
Lector a7b8e7ec0c feat: level and options for automatic and str. rec. dis/advantages in sub-races
Changed automatic dis/advantages in the sub-race so they take the same
parameters as the other dis/advantages.

Closes #150
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-08 15:05:38 +02:00
Lukas Obermann 13c0d5c6ba build(release): 0.47.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-08 10:53:30 +02:00
Lukas Obermann aa556a48c8 feat: optional target category for animist powers 2026-06-08 10:53:25 +02:00
Lukas Obermann fbd29a6e93 build(release): 0.47.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-08 10:46:07 +02:00
Lukas Obermann 9ed6e70645 feat: skill check penalty for animist powers 2026-06-08 10:45:57 +02:00
Lukas Obermann af43af87de fix: personality trait effect supports markdown 2026-06-07 23:22:14 +02:00
Lukas Obermann 99fb31e053 build(release): 0.47.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-07 23:16:29 +02:00
Lukas Obermann 4c2228b6d7 fix!: interval for sustained cost map
Closes #145
2026-06-07 23:16:16 +02:00
Lukas Obermann 737fe3742c feat: fixed cantrips for base profession package
Closes #148
2026-06-07 23:13:07 +02:00
Lukas Obermann 1f28470b8f feat: note for commonness-rated entries
Closes #146
2026-06-07 23:10:47 +02:00
Lukas Obermann d4ffbb9111 feat!: personality trait as activatable prerequisite 2026-06-07 23:08:43 +02:00
Lukas Obermann 0284b1dbae build(release): 0.46.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-07 12:42:48 +02:00
Lukas Obermann 47f9e890bb feat!: allow blessed profession 2026-06-07 12:42:40 +02:00
Lukas Obermann 96d70f7237 fix: required language specializations
Language specializations must be as they are a child entity and cannot
be displayed otherwise.
2026-06-07 12:42:17 +02:00
Lukas Obermann a9ea59a802 feat: use library name as display name if available 2026-06-06 15:15:33 +02:00
Lector ae45659ff9 feat: plants entity
Refs: #142
Closes #3
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-06-04 13:45:30 +02:00
Lukas Obermann 2be0fb3c50 docs: explain responsive text in module doc 2026-06-03 15:21:38 +02:00
Lukas Obermann c3654151f0 build(release): 0.45.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-31 15:07:14 +02:00
Lukas Obermann 64e7ed1035 feat!: add maximum sr translation 2026-05-31 15:07:11 +02:00
Lukas Obermann 84d5023ea3 build(release): 0.44.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-31 13:00:11 +02:00
Lukas Obermann c147d16130 feat: full commonness-rated text replacement 2026-05-31 13:00:05 +02:00
Lukas Obermann 97671ca215 build(release): 0.44.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-30 09:54:08 +02:00
Lukas Obermann 7fc5fd144a feat!: diease and poison options can be required 2026-05-30 09:54:05 +02:00
Lukas Obermann d42242cc64 build(release): 0.43.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-30 09:46:48 +02:00
Lukas Obermann ce8f55b9d6 feat: commonness-rated options 2026-05-30 09:46:41 +02:00
Lukas Obermann ee3490218d build(release): 0.43.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-30 09:34:46 +02:00
Lukas Obermann 2b99d8c994 feat: commonness-rated level 2026-05-30 09:34:42 +02:00
Lukas Obermann d7f8393384 build(release): 0.43.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-29 14:52:30 +02:00
Lukas Obermann 1c8e84c082 build(deps): upgrade all dependencies 2026-05-29 14:52:20 +02:00
Lukas Obermann e0d27d3f7b build(release): 0.43.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-29 14:50:08 +02:00
Lector 7afc6b0326 feat!: auto/strong dis/advantages in race variantFeature/race advantage variant
This is a breaking change. Patch for the data is elyukai/optolith-data#1291

Refs: #139
Closes #138
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-05-29 11:59:05 +02:00
Lukas Obermann c2686593cb feat: printed values for lesson packages
Including the AP value and skill base values helps uncovering
inconsistencies and errors in the source books.
2026-05-29 11:52:06 +02:00
Lukas Obermann 18ab60e356 build(vscode): fix task commands 2026-05-29 11:51:06 +02:00
Lukas Obermann 21780be505 feat!: add translation for name of spellcasters
This is required for generating some text for magical traditions.
2026-05-29 11:50:45 +02:00
Lukas Obermann 57b1916f21 feat!: add translations for favored skills 2026-05-29 11:49:55 +02:00
Lukas Obermann 7d32741cd6 build(release): 0.42.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-04-10 21:36:49 +02:00
Lukas Obermann 0c13fbae99 perf: remove unnecessary filters in constraint
They are already filtered at the end of the whole array.
2026-04-10 21:25:34 +02:00
Lukas Obermann 4a6648e307 feat: add replacement text option to known item specifics 2026-04-10 21:18:53 +02:00
Lukas Obermann 0e62d30607 build(release): 0.42.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-04-10 20:04:10 +02:00
Lukas Obermann 669649ed64 build: fix repository url 2026-04-10 20:04:04 +02:00
Lukas Obermann ab075e11ff build(release): 0.42.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-04-10 19:59:26 +02:00
Lukas Obermann 9a9a2bd6a1 feat: known item specifics 2026-04-10 19:57:40 +02:00
Lukas Obermann 28ffda4046 ci: optimize and upgrade jobs and actions 2026-04-01 21:40:26 +02:00
Lukas Obermann a41dbcfc4c build: rename package 2026-04-01 21:39:52 +02:00
Lukas Obermann 002a807b5b build(release): 0.42.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-31 11:40:40 +02:00
Lector 2dd0be7197 feat: add Bannzeichen entity
Refs: #135
Closes #133
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-24 11:06:51 +01:00
Lector 35dbe49c60 docs: fix referenced file name
_IdentifierGroup.ts instead of Identifier.ts
2026-03-23 19:12:55 +01:00
Lector 78dcb9a737 feat: add goblin rituals entity
Refs: #132
Closes #131
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-23 19:11:21 +01:00
Lukas Obermann 72198eec2a build(release): 0.42.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-23 15:50:33 +01:00
Lukas Obermann 2c531ce8df docs: linting and formatting 2026-03-23 15:50:15 +01:00
Lector 5c8a164428 feat: add familiars trick magical tradition restriction
Refs: #126
Closes #106
Reviewed-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-23 15:45:45 +01:00
Lukas Obermann 849fca45db feat!: adjust translations for 0.41.0 schema changes 2026-03-23 14:59:27 +01:00
Lukas Obermann 7d8bcd89ba feat!: do not confuse rules tab with rules term in entry texts 2026-03-23 14:59:00 +01:00
Lukas Obermann daa049cea9 docs: add tasks for new entity declaration 2026-03-23 10:04:34 +01:00
Lukas Obermann 99466ed0ee feat!: require hair and eye color specifications 2026-03-22 12:00:39 +01:00
Lukas Obermann 750ddb1548 feat: hair color label and hair color count
Closes #116
2026-03-22 12:00:17 +01:00
Lukas Obermann bd931ffcb6 feat: skill rating as check-result-based base value
Closes #125
2026-03-22 11:50:02 +01:00
Lukas Obermann 5d7ced6e88 build(release): 0.41.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-21 14:38:55 +01:00
Lukas Obermann 937eeb94e0 feat!: allow activatable prerequisites for geode rituals
Closes #124
2026-03-21 14:28:47 +01:00
Lukas Obermann c3087b456b feat: magical melody cost interval
Closes #123
2026-03-21 14:27:27 +01:00
Lukas Obermann d4008b3fcf feat!: childhood and magical disease flags
Closes #122
2026-03-21 14:23:07 +01:00
Lukas Obermann ed6cec9997 feat!: attribute adjustments can vary by race variant
Moves the attribute adjustments property into race variants.

Closes #117
2026-03-21 14:19:00 +01:00
Lukas Obermann b172b9f331 feat!: optional ceremonial item structure points
Closes #115
2026-03-21 14:16:53 +01:00
Lukas Obermann 47cdc55aa0 feat!: allow enhancements as profession special abilities
Closes #114
2026-03-21 14:15:34 +01:00
Lukas Obermann 4f5ed7679f feat: specific skills for profession skill options
Closes #97
2026-03-21 14:04:55 +01:00
Lukas Obermann d36c4b8334 feat!: allow weapons as ammunition
Closes #90
2026-03-21 13:47:48 +01:00
Lukas Obermann 00966f9186 feat!: multiple weapon reaches
Closes #89
2026-03-21 13:45:23 +01:00
Lukas Obermann 962950d4c5 feat!: reach position 2026-03-21 13:44:53 +01:00
Lukas Obermann 6a05b6d870 docs: specify branch and commit naming conventions 2026-03-21 11:22:41 +01:00
Lukas Obermann 0b203a5fa7 docs: link to CONTRIBUTING from README 2026-03-21 11:22:18 +01:00
Lukas Obermann 8b8a1f5805 docs: initial contributing guide 2026-03-21 11:15:43 +01:00
Lukas Obermann 4f0b8a7f72 build(deps): upgrade all dependencies 2026-03-21 10:59:56 +01:00
Lukas Obermann 9a5ae50280 feat!: derived characteristic type
Specific roles or types of a derived characteristic may be specified.
Currently, these are energy and resistance. Energy-specific values have
been moved from the calculation property to the energy enum case.
2026-03-21 10:58:52 +01:00
Lukas Obermann ee11a36051 refactor!: id-connected race base values
Connects base values by identifier instead of hard-coded derived
characteristic values.

Also adds a custom containt that races are checked if the contain all
base values that are used in derived characteristic calculation. I.e. if
a derived characterisic contains a race base value in their calculation,
each race must provide a base value for it.
2026-03-21 10:57:25 +01:00
Lukas Obermann 0375cd5d2a feat!: atomic talisman activation values
Includes support for fate points as cost unit.
2026-03-21 10:45:54 +01:00
Lukas Obermann fb9c15c762 refactor: delete old generated artifacts 2026-03-20 15:52:34 +01:00
Lukas Obermann 30db26fc6b feat!: plain quality level effect
Instead of per quality level, the quality levels may be defined as a
single effect text.

Closes #56
2026-03-20 10:51:25 +01:00
Lukas Obermann eafeb5de36 feat!: animal poison can have a complexity
This wraps the previous trade secret value, which is now available for
complexity values of Complex.

Closes #48
2026-03-20 10:36:21 +01:00
Lukas Obermann 03f33ce36d refactor!: unify poison times as expression 2026-03-20 10:25:57 +01:00
Lukas Obermann 1bfcc54fdc build(release): 0.40.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-15 16:13:42 +01:00
Lukas Obermann 7712c1b9bd feat!: translations for style and advanced special abilities 2026-03-15 16:13:39 +01:00
Lukas Obermann 225e59e85d build(release): 0.39.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-14 23:25:28 +01:00
Lukas Obermann 960f1a9a36 feat!: more translations for equipment and professions 2026-03-14 23:25:24 +01:00
Lukas Obermann ac9988253a build(release): 0.38.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-14 18:41:04 +01:00
Lukas Obermann 8774da6c72 feat!: reconstruction and references can differ per edition 2026-03-14 18:40:46 +01:00
Lukas Obermann e3eaca0c25 feat: display names for new skill applications and uses 2026-03-14 18:40:24 +01:00
Lukas Obermann c95cdb860a docs: clarify what is considered a breaking change for this library 2026-03-14 18:27:32 +01:00
Lukas Obermann 0c0ef590ec build(release): 0.37.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-14 13:22:32 +01:00
Lukas Obermann 04b3a7451e feat!: more detailed book rules and meta data 2026-03-14 13:22:27 +01:00
Lukas Obermann 20da25b35e build(release): 0.36.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-13 20:35:01 +01:00
Lukas Obermann 127cc6cc48 feat: cost map style 2026-03-13 20:34:57 +01:00
Lukas Obermann fcd98894e1 build(release): 0.36.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-13 19:56:20 +01:00
Lukas Obermann 5f5785db79 feat!: style for time units
This allows translations to exclude the time value in intervals if the
value is 1.
2026-03-13 19:56:12 +01:00
Lukas Obermann fedf0f0534 build(release): 0.35.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-13 17:12:25 +01:00
Lukas Obermann a83b27e802 feat!: only allow numeric cost for jewelry 2026-03-13 17:11:30 +01:00
Lukas Obermann 8c138459cf build(release): 0.34.17
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-12 23:13:29 +01:00
Lukas Obermann 4d3b548c70 feat: add two translations 2026-03-12 23:13:21 +01:00
Lukas Obermann 506228e181 build(release): 0.34.16
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-12 20:22:16 +01:00
Lukas Obermann 5c11a0f59c feat: add translations for enhancements 2026-03-12 20:22:10 +01:00
Lukas Obermann 79d1761a86 build(release): 0.34.15
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-12 13:56:27 +01:00
Lukas Obermann 8f59744409 feat: add translations for recent additions 2026-03-12 13:56:16 +01:00
Lukas Obermann 91a71437e0 build(release): 0.34.14
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-11 16:20:28 +01:00
Lukas Obermann 4179cde793 build(deps): upgrade tsondb to 0.20.0 2026-03-11 16:20:15 +01:00
Lukas Obermann 3987250013 build(release): 0.34.13
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-11 10:14:14 +01:00
Lukas Obermann 21e033e016 feat: alchemical poison can be demonic as well 2026-03-11 10:13:53 +01:00
Lukas Obermann bfeea905b9 build(release): 0.34.12
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-10 22:21:54 +01:00
Lukas Obermann 3239e09539 feat: optional language and script for books 2026-03-10 22:21:42 +01:00
Lukas Obermann 1dc3b81c83 build(deps): upgrade all dev dependencies 2026-03-10 22:18:52 +01:00
Lukas Obermann a7c394dfa1 build(deps): upgrade tsondb to 0.19.18 2026-03-10 22:17:39 +01:00
Lukas Obermann 96a9f0d1d5 build(release): 0.34.11
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-10 16:29:15 +01:00
Lukas Obermann f8a174efad feat: working supernatural creature entity 2026-03-10 16:29:06 +01:00
Lukas Obermann 6c18477d01 build(release): 0.34.10
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-10 14:58:25 +01:00
Lukas Obermann 22e6dfda8b feat: poison cost depending on purchase or sale 2026-03-10 14:54:58 +01:00
Lukas Obermann 4eb999bf73 build(release): 0.34.9
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 17:18:21 +01:00
Lukas Obermann d519e662b4 feat: add circle of damnation as possible poison duration component 2026-03-09 17:18:05 +01:00
Lukas Obermann 64f29e440b build(release): 0.34.8
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 16:10:51 +01:00
Lukas Obermann 17d46f17b4 fix(cache): handle added poison source type 2026-03-09 16:10:39 +01:00
Lukas Obermann 2f833090a3 build(release): 0.34.7
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 16:03:31 +01:00
Lukas Obermann c32cb594aa feat: add alchemical pact gift poison source type and make cost optional
Fixes #88
2026-03-09 16:02:08 +01:00
Lukas Obermann d14639711c build(release): 0.34.6
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 14:48:41 +01:00
Lukas Obermann f97e9dce22 feat: general rules for influences 2026-03-09 14:48:29 +01:00
Lukas Obermann 778bbe3578 build(release): 0.34.5
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 14:18:18 +01:00
Lukas Obermann 1e08553eae feat: support markdown in influence effect texts 2026-03-09 14:18:13 +01:00
Lukas Obermann e857decb1c build(release): 0.34.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 14:01:09 +01:00
Lukas Obermann 7404637f29 feat: rule precondition 2026-03-09 14:01:05 +01:00
Lukas Obermann c7f862b4cb feat: profession influence prerequisites 2026-03-09 14:00:54 +01:00
Lukas Obermann 5499ffef5b build(release): 0.34.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-09 13:48:44 +01:00
Lukas Obermann bc41f2e7d1 feat: add race and activatable influence prerequisites 2026-03-09 13:43:13 +01:00
Lukas Obermann 44d121df61 docs: clarify reduced poison/disease values 2026-03-09 13:42:55 +01:00
Lukas Obermann be9a69e708 style: sort colors alphabetically 2026-03-08 17:24:09 +01:00
Lukas Obermann ff479f01f3 build(release): 0.34.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-08 17:22:41 +01:00
Lukas Obermann fa4c6f820b feat: make optional rule complexity influence optional 2026-03-08 17:22:35 +01:00
Lukas Obermann a2135e4626 build(release): 0.34.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-08 16:40:11 +01:00
Lukas Obermann 5c5d987b94 build(deps): upgrade tsondb to fix build 2026-03-08 16:40:03 +01:00
Lukas Obermann 72412103d3 build(deps): upgrade all deps 2026-03-08 15:58:01 +01:00
Lukas Obermann 13adf17ce5 build(release): 0.34.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-03-08 15:56:23 +01:00
Lukas Obermann 8aedafb813 feat!: define attribute and skill group colors 2026-03-08 15:53:50 +01:00
Lukas Obermann b2f1c5dfd6 feat!: atomic specification of how an optional rule influences game complexity 2026-03-08 15:53:39 +01:00
Lukas Obermann 75bc15b4ad build(release): 0.33.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-25 22:02:59 +01:00
Lukas Obermann 94cf3e5331 build(deps): upgrade tsondb 2026-02-25 22:02:52 +01:00
Lukas Obermann f3023a2f9e build(deps): upgrade all dependencies 2026-02-25 17:59:02 +01:00
Lukas Obermann 7b2882322c feat: add various translations
Add translations for races, cultures, professions and influence
prerequisties.
2026-02-25 17:58:43 +01:00
Lukas Obermann 9fcb303452 feat!: require blessed ones' name
This is required for rendering recommended blessed professions. The
database currently allows traditions to be selected, which would mark
professions that require that tradition as common for that culture.
2026-02-25 13:45:29 +01:00
Lukas Obermann 6154058d65 feat!: required skill group for free AP distribution on skills
It has not been used without a specified group so far and this change
makes sure that only what is known is rendered in a way that matches the
texts in the official books.
2026-02-25 13:43:24 +01:00
Lukas Obermann fa3be57096 feat!: remove terrain knowledge profession option
It can be replaced with a normal special ability choice option.
2026-02-25 13:41:30 +01:00
Lukas Obermann ab02370c90 feat!: require experience level for profession packages
Also ensure there is only one profession package for a certain
experience level for a profession version.
2026-02-25 13:40:21 +01:00
Lukas Obermann 710d5be33d feat!: remove subgroup option from common mundane professions 2026-02-25 13:39:01 +01:00
Lukas Obermann 1c84c830bc feat!: culture languages can specify specializations 2026-02-25 13:38:36 +01:00
Lukas Obermann c904a00e02 refactor: remove underscore in filename
Influence is a full entity, so the file name should be without an
underscore.
2026-02-25 13:36:53 +01:00
Lukas Obermann badd4aef95 feat: language specialization display name includes language 2026-02-25 13:35:37 +01:00
Lukas Obermann 977f33c068 feat: activatable name builder rules
This adds configuration on how to concatenate the full name of an
activatable entry. Currently, it can be configured where to place the
level and if parenthesis should be rendered around the selected
option(s), but this object can be extended in the future.
2026-02-25 13:35:05 +01:00
Lukas Obermann 3f434a216f build(release): 0.32.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-21 21:03:26 +01:00
Lukas Obermann fcc91ca75d build(deps): upgrade all dependencies 2026-02-21 20:59:47 +01:00
Lukas Obermann db431e25cf feat: add translations for magical actions 2026-02-21 20:51:07 +01:00
Lukas Obermann 9fd01ec1f2 refactor!: streamline types 2026-02-21 20:50:55 +01:00
Lukas Obermann b67198901b refactor: reuse some prerequisite group cases 2026-02-21 20:50:06 +01:00
Lukas Obermann 1481ef8c6b build(release): 0.31.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-16 11:50:09 +01:00
Lukas Obermann a9211ec6b7 fix: optional withdrawal prevention and disease link 2026-02-16 11:50:05 +01:00
Lukas Obermann ffbe1f4a78 build(release): 0.31.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-16 11:00:11 +01:00
Lukas Obermann 30a6a3c426 fix: interpretation and display of poisons/elixirs 2026-02-16 11:00:01 +01:00
Lukas Obermann 3497704df5 build(release): 0.31.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-15 23:22:25 +01:00
Lukas Obermann 3561289fc1 feat: add translations for poisons, elixirs, sex practices, diseases and personality traits 2026-02-15 23:22:21 +01:00
Lukas Obermann c3a61244ce fix: incorrect comment 2026-02-15 23:21:53 +01:00
Lukas Obermann a24aeb6dd1 fix: poison addition interpretation 2026-02-15 23:21:44 +01:00
Lukas Obermann 1d93a00d26 build(release): 0.31.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-15 13:51:21 +01:00
Lukas Obermann fa4b7b5e68 feat: add rest of translations for equipment 2026-02-15 13:51:17 +01:00
Lukas Obermann 3692506cfa build(release): 0.31.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-14 12:54:08 +01:00
Lukas Obermann 3fa10600c2 feat: add some translations for equipment 2026-02-14 12:50:48 +01:00
Lukas Obermann 9040013a84 feat: locale measurement adjustments 2026-02-14 12:50:26 +01:00
Lukas Obermann b46029da1e fix!: use correct entity names in identifier groups 2026-02-14 12:50:02 +01:00
Lukas Obermann 175a379a79 build(release): 0.30.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-13 15:50:04 +01:00
Lukas Obermann 7da65cf3b8 feat: add translations for curricula 2026-02-13 15:49:37 +01:00
Lukas Obermann 499ed4e25a build(release): 0.30.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-12 22:05:39 +01:00
Lukas Obermann f6010fe264 build(deps): upgrade @elyukai/utils to 0.2.2 2026-02-12 22:05:03 +01:00
Lukas Obermann 40c07baa30 feat!: adjust for activatable display
Prepare content to be displayed. This means adding missing translations
string to the Locale type and adjusting existing types to match better
with what is written in the books.

This breaks some existing combat special abilities.
2026-02-12 22:04:47 +01:00
Lukas Obermann e06124a6bc build: add watch command for generating types 2026-02-12 22:04:36 +01:00
Lukas Obermann b2a0d58202 feat: add more translations 2026-02-10 20:40:02 +01:00
Lukas Obermann 78b69fbe70 feat!: require focus rule subjects 2026-02-10 20:40:02 +01:00
Lukas Obermann e0ac1e31e4 feat!: more advanced identifier options
This breaks existing content which only supports flat identifiers.

Closes #120
2026-02-10 20:39:48 +01:00
Lukas Obermann 403d4bc1e3 feat: add optional derived characteristic description 2026-02-10 00:26:37 +01:00
Lukas Obermann 843da629d8 build(release): 0.29.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-09 22:54:41 +01:00
Lukas Obermann 13db5411bc build(deps): upgrade tsondb to 0.19.9 2026-02-09 22:54:34 +01:00
Lukas Obermann 1db8d9b41d refactor: derive identifier groups from others 2026-02-09 22:52:23 +01:00
Lukas Obermann 3c0469e414 feat: include all derived characteristic settings 2026-02-09 22:51:59 +01:00
Lukas Obermann f55bb9f6c2 feat: values and translations for derived characteristic base value calculation 2026-02-09 17:39:50 +01:00
Lukas Obermann 1b86e1c4d3 build(release): 0.29.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-09 14:11:24 +01:00
Lukas Obermann 55d2273223 feat: add some translation strings 2026-02-09 14:11:09 +01:00
Lukas Obermann 0b80c3bf02 feat(cache): export other relevant types 2026-02-09 14:05:33 +01:00
Lukas Obermann 9407cb5767 feat!: adjust cache builder for current database
Also adjusts publication ref values and blessed tradition principle
ref values, which might break current database data.
2026-02-09 14:02:34 +01:00
Lukas Obermann 5b50b860e2 build: remove obsolete files and package experts 2026-02-09 13:59:02 +01:00
Lukas Obermann 22f8e95a76 build(release): 0.28.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-05 21:32:30 +01:00
Lukas Obermann 0a500ffee4 build(deps): upgrade all dependencies 2026-02-05 21:32:22 +01:00
Lukas Obermann 2f1a8c2cbf build(release): 0.28.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-05 20:43:21 +01:00
Lukas Obermann ae034e1bd6 feat: add optional isRemoved flag to initial source refs 2026-02-05 20:42:46 +01:00
Lukas Obermann 72a3384fda feat: nested entity maps should not be empty
Requires all NestedEntityMap types to have at least one entry.
2026-02-05 20:41:29 +01:00
Lukas Obermann c5447d7570 refactor: remove unused translation-related helpers 2026-02-05 20:38:40 +01:00
Lukas Obermann 12893b7d21 build(release): 0.28.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-05 11:16:41 +01:00
Lukas Obermann a0399bcf15 style: use namespace for tsondb dsl imports 2026-02-05 11:13:58 +01:00
Lukas Obermann 5e8200a13a build(deps): upgrade tsondb to 0.19.0
Also adjusts imports according to breaking changes.
2026-02-05 10:41:33 +01:00
Lukas Obermann b7d449a5f0 fix: empty target categories must be possible
Reverts b2516791a1.
2026-02-05 10:34:12 +01:00
Lukas Obermann b2516791a1 fix: require at least one target category 2026-02-02 10:11:01 +01:00
Lukas Obermann 9a60d7c925 build(release): 0.28.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-01 22:44:39 +01:00
Lukas Obermann 99d3bc425c feat!: remodel reversalis
Reversalis may have multiple effects with different properties.
2026-02-01 22:44:28 +01:00
Lukas Obermann 18ddf9a415 build(release): 0.27.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-02-01 22:17:13 +01:00
Lukas Obermann f8ba58ca6f build(deps): upgrade all dependencies 2026-02-01 22:16:58 +01:00
Lukas Obermann 73edb6cb5d feat: add reversalis fields to spells and rituals 2026-02-01 22:15:49 +01:00
Lukas Obermann 5aa42a7242 build(release): 0.27.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2026-01-29 11:23:54 +01:00
Lukas Obermann 5c253b8213 build(deps): upgrade all deps and replace helpers
Upgrade all dependencies and replace @optolith/helpers with
@elyukai/utils.
2026-01-29 11:17:08 +01:00
Lukas Obermann ae73881924 build(deps): upgrade tsondb to 0.18.0
Also fixes profession display name customizer function.
2026-01-29 11:15:22 +01:00
Lukas Obermann 1f8823fa5f fix: talisman combat use nesting and constraint 2026-01-26 13:08:02 +01:00
Lukas Obermann aca931d668 feat!: custom sort orders and position values
Some entities have a distinct sort order. This makes it possible to keep
them sorted the same way. This also affects the sort order in the
editor.

BREAKING CHANGE: The position values need to be added. Also, the
"order" value for social statuses has been renamed to "position" to
align with other specially sorted entities.
2026-01-19 18:11:40 +01:00
Lukas Obermann d97b878172 build(deps): upgrade tsondb and register other maps
Upgrade tsondb to 0.17.6.
Generate helper maps for enums and type aliases.
Register helper maps in interface.
Prepare custom constraint for printing occurrences.
2026-01-03 17:28:13 +01:00
Lukas Obermann fb251d1bb6 feat: allow deprecation as only source reference
Prepare for unified occurrences array (via PrintingOccurrence).
2026-01-02 23:29:12 +01:00
Lukas Obermann 6feea9f209 build(deps): upgrade tsondb
Upgrade tsondb to 0.17.4
2026-01-01 21:15:00 +01:00
Lukas Obermann d42253c483 fix: add type parameter name 2026-01-01 21:14:36 +01:00
Lukas Obermann 8af09109ba fix: make unique constraint work with any damage 2025-12-30 13:57:21 +01:00
Lukas Obermann 5a1fc2f2f5 feat: talisman melee and ranged uses
Closes #60
2025-12-30 13:51:02 +01:00
Lukas Obermann 21b8fcc7d2 feat: automatic race disadvantages
Closes #113
2025-12-30 12:03:14 +01:00
Lukas Obermann 260f74dcd2 feat: allow options text with reference
Allows the additions of specific options text directly with the
reference, which allows for better text generation and display of
applying options in Optolith.
2025-12-30 11:59:24 +01:00
Lukas Obermann 5ab382de3b refactor: remove unused entity-bound select options 2025-12-30 11:40:11 +01:00
Lukas Obermann 0a073b5d67 fix: src comment and display name 2025-12-29 18:51:53 +01:00
Lukas Obermann 1b6ad8b535 build(release): 0.26.5
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-29 18:45:33 +01:00
Lukas Obermann 6cb3f40f7e feat: add some display names 2025-12-29 18:45:28 +01:00
Lukas Obermann 6fad16bf98 fix: duplicate cost map translation identifier 2025-12-29 18:41:27 +01:00
Lukas Obermann e7f8517e8a build(release): 0.26.4
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-29 18:36:48 +01:00
Lukas Obermann 8a4903ebdc feat: warding circle text is optional
Closes #105
2025-12-29 18:36:07 +01:00
Lukas Obermann ae43d2ab12 feat: add rated option to enhancement prerequisite
Closes #103
2025-12-29 18:33:49 +01:00
Lukas Obermann 1fd987de4f feat: add sustained skill cost map
Closes #102
2025-12-29 18:32:08 +01:00
Lukas Obermann 89652bfe85 style: sort imports 2025-12-29 18:30:07 +01:00
Lukas Obermann 68ea4f6955 build(deps): upgrade tsondb
Upgrade tsondb to 0.17.0
Adjust all schema definitions for the upgrade.
2025-12-29 18:29:55 +01:00
Lukas Obermann f3ea105280 feat: add object as skill check penalty
Closes #101
2025-12-29 17:32:22 +01:00
Lukas Obermann a60b193ef7 style: make enum cases without value one-line 2025-12-29 17:31:47 +01:00
Lukas Obermann e3bd65f711 feat: add race as requirable select option
Closes #99
2025-12-29 17:29:29 +01:00
Lukas Obermann 2800c9d4bf build(release): 0.26.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-28 19:56:58 +01:00
Lukas Obermann 13413e236f feat: add spellworks as target category option
Closes #100
2025-12-28 19:56:07 +01:00
Lukas Obermann 498014a55a fix: general select option display name 2025-12-28 19:51:25 +01:00
Lukas Obermann 876139646b build(release): 0.26.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-23 15:00:27 +01:00
Lukas Obermann 59a6d4119d fix: add homunculus type to schema 2025-12-23 15:00:16 +01:00
Lukas Obermann b0ace555b1 build(release): 0.26.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-23 14:55:26 +01:00
Lukas Obermann 8eaab37e9b feat: streamline trade secret select options 2025-12-23 14:55:21 +01:00
Lukas Obermann 8062836e9c feat: allow homunculus types as derived select options 2025-12-23 14:54:18 +01:00
Lukas Obermann 4fb42d3f84 feat: add homunculus type entity 2025-12-23 14:52:22 +01:00
Lukas Obermann 2dc62b5907 feat: customize AP value string generation
Add listPrepend option just like for explicit cost maps.
2025-12-21 13:00:15 +01:00
Lukas Obermann b2c23c7099 fix: require binding cost map option translations 2025-12-21 12:59:19 +01:00
Lukas Obermann c8de5a74bd feat: support binding cost generation for select options 2025-12-21 12:59:00 +01:00
Lukas Obermann fd52e85256 fix: note on profession spells and liturgies
Closes #98
2025-12-16 10:38:34 +01:00
Lukas Obermann 53d43ea101 build(release): 0.26.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-15 14:26:07 +01:00
Lukas Obermann 02be29dab7 feat!: custom constraint for weapon values
This checks values provided for each combat technique if it matches the
values the combat technique requires.

BREAKING CHANGE: Combat techniques need to specify special rules
differently.
2025-12-15 14:25:50 +01:00
Lukas Obermann a1316af0c1 feat: add profession liturgies for ap sum option
Closes #86
2025-12-15 13:01:03 +01:00
Lukas Obermann fdf04eea25 build(deps): upgrade tsondb
Upgrade tsondb to 0.16.0
2025-12-15 12:57:28 +01:00
Lukas Obermann fb056e8013 build(deps): upgrade tsondb
Upgrade tsondb to 0.15.0. Adjusts CI config and type config accordingly.
2025-12-14 21:26:42 +01:00
Lukas Obermann 3c7ad59809 build(release): 0.25.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-14 16:06:34 +01:00
Lukas Obermann 6086a6d891 feat: adjust display names of pact domains and types 2025-12-14 16:05:26 +01:00
Lukas Obermann 8bacb489d8 feat!: add unique constraints
Upgrades tsondb for that support, which also changes the config shape.
2025-12-14 16:04:14 +01:00
Lukas Obermann 05465711d2 test: display schema errors not as runtime errors 2025-12-13 00:36:52 +01:00
Lukas Obermann b0cae16a9a build(release): 0.24.3
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-10 00:15:18 +01:00
Lukas Obermann df21ff3e99 fix: profession variants could not be entered 2025-12-10 00:12:30 +01:00
Lukas Obermann f14ecf01b4 feat: add raw value option to elixirs
Closes #84
2025-12-09 17:26:37 +01:00
Lukas Obermann fca2ca9d00 build(release): 0.24.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-09 16:27:15 +01:00
Lukas Obermann 7e93d95232 feat: optional weight for gems and precious stones
Closes #78
2025-12-09 16:25:11 +01:00
Lukas Obermann 18e2059118 feat: add one blunt side as an option for specific combat techniques
Closes #79
2025-12-09 16:23:56 +01:00
Lukas Obermann 71f9d5981b fix: achaz enchantments as select option parent
The select option parent identifier group did not allow Achaz
enchantments before, even though they allow select options.
2025-12-09 16:20:09 +01:00
Lukas Obermann 254f683dd2 feat: add input option to advanced combat special abilities
Closes #80
2025-12-09 16:20:09 +01:00
Lukas Obermann b071ac927a feat: add tradition artifact entities for achaz
Closes #81
2025-12-09 16:09:48 +01:00
Lukas Obermann 5f521c113d feat: add personality trait prerequisite to personality traits
Closes #83
2025-12-09 12:57:56 +01:00
Lukas Obermann 6995b8cebd feat: modification level translation in display name 2025-12-08 17:44:00 +01:00
Lukas Obermann 9f5d89db16 build(release): 0.24.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-12-08 15:45:27 +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
Lukas Obermann 356465f0df build(release): 0.18.2
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-04 20:50:08 +02:00
Lukas Obermann 00a0efc6d0 fix: adjust for remaining incompatibilities with source data 2025-10-04 20:50:00 +02:00
Lukas Obermann 2db7ae2903 build(release): 0.18.1
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-03 10:33:05 +02:00
Lukas Obermann 08a023d102 ci: fix test before publish 2025-10-03 10:32:54 +02:00
Lukas Obermann 544d3685a4 build(release): 0.18.0
Signed-off-by: Lukas Obermann <lukas.obermann@icloud.com>
2025-10-03 10:27:25 +02:00
Lukas Obermann af5519d164 build(deps): switch to commit-and-tag-version from unmaintained standard-version 2025-10-03 10:25:08 +02:00
Lukas Obermann 619805ccfb feat!: tsondb integration
Refs: #35
2025-10-03 10:17:25 +02:00
Lukas Obermann 01265b3a37 refactor: remove workaround for ajv import
The ajv package now provides exports that make it possible to not have to use the workaround anymore.

See https://github.com/ajv-validator/ajv/pull/2389
2025-03-30 16:22:46 +02:00
Lukas Obermann 2963d739df style: apply prettier formatting 2025-03-30 16:19:57 +02:00
Lukas Obermann 8f9e839483 refactor: use @optolith/helpers/result instead of local result file 2025-03-30 16:19:27 +02:00
Lukas Obermann f5e0324d3d build(deps): upgrade deps 2025-03-30 16:17:58 +02:00
Lukas Obermann a031ad93ea feat: add translation strings for prerequisites 2025-03-30 16:17:07 +02:00
Lukas Obermann 25232282da feat: add skill application identifier as possible select option identifier 2025-03-30 16:16:38 +02:00
Lukas Obermann bb7a688b01 fix!: use identifier object instead of plain number for publication prerequisite identifier 2025-03-30 16:12:59 +02:00
Lukas Obermann f0c2d2aa70 build(npm): replace .npmignore with whitelist 2024-12-16 13:46:37 +01:00
Lukas Obermann 99bdfb6c21 refactor(swift): move renderer to optolith-tsjsonschemamd package 2024-11-27 16:32:18 +01:00
Lukas Obermann f5167bb565 fix(swift): LocaleMap initializer didnt initialize all properties 2024-11-17 18:37:20 +01:00
Lukas Obermann c247c067e4 feat(swift): add set of available locales and documentation for LocaleMap 2024-11-17 18:34:19 +01:00
Lukas Obermann 8f2be47444 fix(swift): identifier objects' initializers must be public 2024-11-17 18:23:20 +01:00
Lukas Obermann dbe19027a0 fix(swift): remove unnecessary indentation in empty lines 2024-11-17 18:19:21 +01:00
Lukas Obermann fc79950428 feat(swift): add initializers to all structs 2024-11-17 18:18:20 +01:00
Lukas Obermann f3602a9398 refactor(swift): configure package name in file header comment in otjsmd.config.js 2024-11-17 18:15:46 +01:00
Lukas Obermann f80bbaa828 fix(swift): add ignored LocaleMap type 2024-11-17 16:07:31 +01:00
Lukas Obermann 6dd361a16c feat(swift): do not generate ignored files 2024-11-17 16:04:06 +01:00
Lukas Obermann 9db2fd5989 feat(swift): remove generation date header comment
This way the renderer stays a pure function.
2024-11-16 17:04:50 +01:00
Lukas Obermann f865a34058 style: remove line breaks in documentation paragraphs 2024-11-16 16:58:45 +01:00
Lukas Obermann 4236fa3804 feat!: adjust naming for compiles languages that dont create namespaces per file
This includes a Proof of Concept in form of a Swift Package. Only one file had to be replaced, because I couldnt make sense of how else to do it. But it compiles.
2024-11-16 02:01:25 +01:00
Lukas Obermann dd999c1387 feat!: add blessed tradition type as union type
This now also includes whether the tradition is capable of using bone maces as ceremonial items.
2024-10-12 16:33:03 +02:00
Lukas Obermann ebeba260cf build(release): 0.17.2 2024-10-06 20:37:13 +02:00
Lukas Obermann 16e2daa6bf ci: add id-token permission for publish job 2024-10-06 20:36:58 +02:00
Lukas Obermann d5dbbcab72 build(release): 0.17.1 2024-10-06 20:32:11 +02:00
Lukas Obermann 1bf8df1575 feat: add minimum and maximum UI strings with placeholders 2024-10-06 20:31:59 +02:00
Lukas Obermann 44625e7d62 build(deps): upgrade @optolith/helpers to 0.2.1 2024-10-06 12:44:15 +02:00
Lukas Obermann 9bf45ec32d style: apply prettier to changed files 2024-10-06 12:34:54 +02:00
Lukas Obermann 556a1e1581 style: use return with assertExhaustive 2024-10-06 12:34:39 +02:00
Lukas Obermann d8324f7042 feat: use @optolith/helpers package 2024-10-06 12:34:04 +02:00
Lukas Obermann a678ad216c ci: publish provenance alongside the package 2024-10-06 12:28:38 +02:00
Lukas Obermann af88911932 ci: upgrade used node version to 22 2024-10-06 12:25:42 +02:00
Lukas Obermann 4f83c9b7ec ci: upgrade actions 2024-10-06 12:25:17 +02:00
Lukas Obermann a8340e6dd4 feat: add pluralization type definitions 2024-10-04 14:00:36 +02:00
Lukas Obermann 7d4c8e55cd build(release): 0.17.0 2024-10-03 22:25:35 +02:00
Lukas Obermann 223a469a63 feat!: add translations for library entries of spellworks and liturgical chants
This also includes now using responsive text for infinite blessing duration descriptions.

Also this fixes two previously used translation keys that have not been updated.
2024-10-03 22:24:32 +02:00
Lukas Obermann e28b0c35e6 build(release): 0.16.8 2024-03-01 14:06:24 +01:00
Lukas Obermann ef91f897ad fix: add slash to protective and warding circle key 2024-03-01 14:06:20 +01:00
Lukas Obermann 8b1893a4ae build(release): 0.16.7 2024-03-01 14:05:43 +01:00
Lukas Obermann aaa92c577d build(release): 0.16.6 2024-03-01 13:50:39 +01:00
Lukas Obermann 5b95dd52e1 feat: add special ability group translations 2024-03-01 13:50:26 +01:00
Lukas Obermann 4a9768d949 build(release): 0.16.5 2024-02-29 18:28:36 +01:00
Lukas Obermann ae07fd2b6d feat: convert many used keys to their english fallback and add some new keys 2024-02-29 18:28:28 +01:00
Lukas Obermann eba871a0e7 fix: add missing identifier handling to magical and blessed advantages and disadvantages cache 2024-02-29 18:27:50 +01:00
Lukas Obermann c21b851281 fix: applications and uses for select options 2024-02-29 18:27:18 +01:00
Lukas Obermann a34676b3c3 feat: add missing activatable and rated identifiers 2024-02-29 18:26:58 +01:00
Lukas Obermann a081b9da4d build(release): 0.16.4 2024-01-21 12:43:49 +01:00
Lukas Obermann 4dc6e3dec7 feat: magical and blessed advantages and disadvantages identifier cache 2024-01-21 12:43:21 +01:00
Lukas Obermann b44929aa70 build(release): 0.16.3 2024-01-18 22:16:31 +01:00
Lukas Obermann 829da0079a fix: resolve intersection types
Intersections dont work when setting the "allowAdditionalProperties" parser option to false.
2024-01-18 22:16:27 +01:00
Lukas Obermann 7075323cd1 build(release): 0.16.2 2024-01-18 13:52:05 +01:00
Lukas Obermann 3d482bbe82 refactor: rename some translation strings 2024-01-18 13:52:00 +01:00
Lukas Obermann cb11b32278 fix: update build artifacts 2024-01-18 13:51:27 +01:00
Lukas Obermann 3e44b6fe6e build(release): 0.16.1 2024-01-16 17:05:21 +01:00
Lukas Obermann 75904cf376 build(deps): upgrade optolith-tsjsonschemamd to 0.10.6 2024-01-16 17:05:18 +01:00
Lukas Obermann 9ccb3afc1a build(release): 0.16.0 2024-01-12 22:14:16 +01:00
Lukas Obermann aea4586074 fix!: remove GenericIdentifier in favor of GeneralIdentifier 2024-01-12 22:14:11 +01:00
Lukas Obermann 59398627fc build(release): 0.15.5 2024-01-12 15:28:43 +01:00
Lukas Obermann b850419229 build: add cache folder to package exports 2024-01-12 15:28:38 +01:00
Lukas Obermann a6bca83ab9 build(release): 0.15.4 2024-01-12 15:25:01 +01:00
Lukas Obermann 3fdcaf2a95 feat: add pre-generated select options for activatable entries as cache 2024-01-12 15:23:59 +01:00
Lukas Obermann a70a7ea4a0 fix: wrong identifier references in select option config 2024-01-12 15:23:13 +01:00
Lukas Obermann 0ee088a01b feat: add possible and requirable select option identifiers 2024-01-12 15:22:15 +01:00
Lukas Obermann 98316b63a9 fix: add missing vampiric gift identifier to activatable identifier group 2024-01-12 15:21:38 +01:00
Lukas Obermann ab32931aa6 build(deps): upgrade optolith-tsjsonschemamd to 0.10.5 2024-01-12 15:19:31 +01:00
Lukas Obermann c76ab56cb9 build(release): 0.15.3 2024-01-09 21:22:29 +01:00
Lukas Obermann 6c1f01578a style: configure prettier 2024-01-09 21:22:23 +01:00
Lukas Obermann 8f06497046 feat(cache): add ancestor blood advantages list as cache 2024-01-09 21:22:10 +01:00
Lukas Obermann 4319a98977 build(release): 0.15.2 2024-01-08 20:24:16 +01:00
Lukas Obermann 7247292c9e fix: rated sum targets should only be skill identifiers 2024-01-08 20:24:08 +01:00
Lukas Obermann da99b5308c build(release): 0.15.1 2024-01-06 16:39:00 +01:00
Lukas Obermann b0e0bddf32 feat: group abbreviated and full adventure points translations with and without preceding value 2024-01-06 16:38:55 +01:00
Lukas Obermann b0c95e75df build(release): 0.15.0 2023-12-29 19:32:03 +01:00
Lukas Obermann 04a57e8bc4 feat: type config type includes type name literal 2023-12-29 19:31:48 +01:00
Lukas Obermann d6395ea8f5 refactor!: convert core rule identifier to plain integer 2023-12-29 19:31:44 +01:00
Lukas Obermann 266fe4cb3f build(release): 0.14.0 2023-12-27 20:17:41 +01:00
Paul Panther 95768f3013 fix!: remove equipment category Alchemicum
Refs: #32
2023-12-27 20:14:51 +01:00
Paul Panther 98993f7c35 fix: add elixir identifier 2023-12-27 20:14:22 +01:00
Paul Panther 0cf9d98890 feat: add optional "notes" field to poison translation
Refs: #28
2023-12-27 20:12:41 +01:00
Paul Panther 0581049f28 feat: add poison duration type "instant" 2023-12-27 20:12:35 +01:00
Paul Panther 7ad54e1c16 fix: weapons should require a complexity 2023-12-27 20:11:44 +01:00
Lukas Obermann 6f5391ee88 build(release): 0.13.8 2023-12-07 15:25:42 +01:00
Lukas Obermann 3ab0afd505 build(deps): upgrade optolith-tsjsonschemamd to 0.10.4 and update artifacts 2023-12-07 15:25:33 +01:00
Lukas Obermann a5d9c5b083 build(release): 0.13.7 2023-12-05 18:51:29 +01:00
Lukas Obermann 10cf5ad704 build(config): do not use value from optolith-tsjsonschemamd 2023-12-05 18:51:24 +01:00
Lukas Obermann 18cc6ea738 build(release): 0.13.6 2023-12-05 18:40:10 +01:00
Lukas Obermann cad7381ec0 build(config): do not use value from optolith-tsjsonschemamd 2023-12-05 18:40:07 +01:00
Lukas Obermann f502778542 build(release): 0.13.5 2023-12-05 18:38:53 +01:00
Lukas Obermann 2fd85cd780 build(deps): upgrade optolith-tsjsonschemamd to 0.10.2 2023-12-05 18:38:47 +01:00
Lukas Obermann 74aca44a89 build(release): 0.13.4 2023-12-01 19:16:58 +01:00
Lukas Obermann 8e6dab0907 fix: musical instruments may have a complexity and combat use 2023-12-01 19:16:50 +01:00
Lukas Obermann 13a3b530f4 fix: make complexity optional for natural weapons 2023-12-01 19:16:28 +01:00
Lukas Obermann 1d53d3a4d5 build: use new tsjsonschema imports 2023-12-01 19:15:21 +01:00
Lukas Obermann 9dfd55c067 build(deps): fix optolith-tsjsonschemamd being local 2023-11-14 18:00:34 +01:00
Lukas Obermann 6c4d0a354f build(deps): upgrade optolith-tsjsonschemamd to 0.10.0 and rebuild assets 2023-11-09 21:49:03 +01:00
Lukas Obermann 5c38b6c7c8 build(release): 0.13.3 2023-11-01 12:47:02 +01:00
Lukas Obermann 0c5ba3981e feat: ajv configuration options 2023-11-01 12:46:57 +01:00
Lukas Obermann 0aecc852b7 build(release): 0.13.2 2023-10-31 20:05:01 +01:00
Lukas Obermann 8d1b5b68ee build(deps): upgrade all dependencies 2023-10-31 20:04:54 +01:00
Lukas Obermann 686d41bd1f build(release): 0.13.1 2023-10-31 20:02:26 +01:00
Lukas Obermann 0fe96bcb9f feat: add strings for main window and spellwork/magical action categories 2023-10-31 20:02:13 +01:00
Lukas Obermann 952a0aa5bc feat: translation strings can vary by operating system 2023-10-31 20:02:01 +01:00
Lukas Obermann 8f61dbfd70 build(release): 0.13.0 2023-10-03 11:24:55 +02:00
Lukas Obermann 3068ee94ed feat!: change and add lots of new translations with new key format
The key is now a proper English text, so it can also be used as a fallback string.
2023-10-03 11:24:46 +02:00
Lukas Obermann 39cbbea2e9 build(deps): upgrade optolith-tsjsonschemamd to 0.9.4 2023-10-03 11:22:34 +02:00
Lukas Obermann 995c65db2b docs: fix bare url 2023-08-17 17:27:49 +02:00
Lukas Obermann 45cf7bfd3e feat: cache capability
This adds the option to build a cache from the database so that it doesn’t have
to be re-calculated every time. The first provided cache is a collections of new
applications and uses for skills.
2023-08-17 17:02:31 +02:00
Lukas Obermann 58c37c440e docs: rewrite and add a lot of sections based on additional Discord content 2023-08-17 15:05:37 +02:00
Lukas Obermann 802349ea5b fix: typo in getFilenamePrefixAsNumericId 2023-08-17 11:28:48 +02:00
Lukas Obermann 8689ec392f refactor: resolve discriminated union and separate attribute adjustments 2023-08-17 11:16:28 +02:00
Lukas Obermann fe5aa116f8 refactor!: replace all enums with union types to enable type-only usage
Enums require the library to be included in a bundle, which can be difficult in web environments.
2023-06-15 19:58:17 +02:00
Lukas Obermann 48b9963b8e build(release): 0.12.1 2023-06-11 18:09:17 +02:00
Lukas Obermann c4bf451195 feat: provide function to get raw validation results 2023-06-11 18:08:35 +02:00
Lukas Obermann 178ef85006 style: rename parameter to match the same in main.ts 2023-06-11 18:06:18 +02:00
Lukas Obermann 8e3915f716 fix: use different file name validation patterns per type
Locale and UI types use the locale identifier as their file name.
2023-06-11 18:05:43 +02:00
Lukas Obermann 7aff5ef453 build(release): 0.12.0 2023-06-03 22:28:25 +02:00
Lukas Obermann 79d8381c1b feat!: rename header tab keys and add button names 2023-06-03 22:27:56 +02:00
Lukas Obermann afb301ad5b build(release): 0.11.9 2023-05-31 14:56:22 +02:00
Lukas Obermann 940f5674f6 fix: move TypeId declaration to exported file 2023-05-31 14:56:16 +02:00
Lukas Obermann 9aad126035 build(release): 0.11.8 2023-05-31 14:52:53 +02:00
Lukas Obermann dcb95e8ee0 fix(exports): config exports did not match new folder structure 2023-05-31 14:52:49 +02:00
Lukas Obermann 022d953003 build(release): 0.11.7 2023-05-30 10:30:32 +02:00
Lukas Obermann 62b0eb0fda fix: restrict ValidResults types to each entity's type 2023-05-30 10:30:26 +02:00
Lukas Obermann 81e9932aae build(release): 0.11.6 2023-05-28 17:09:32 +02:00
Lukas Obermann 22c18aee50 build(npm): adjust ignored files to code generation via cli 2023-05-28 17:09:17 +02:00
Lukas Obermann fd6c2d0a9c build(ts): remove --build option from scripts 2023-05-28 17:08:22 +02:00
Lukas Obermann ff382fb9a6 build(deps): upgrade optolith-tsjsonschemamd to 0.9.2 and use its new cli 2023-05-28 17:05:36 +02:00
Lukas Obermann 9b42d8c263 build(ts): remove unused allowSyntheticDefaultImports option 2023-05-28 17:04:26 +02:00
Lukas Obermann 0617e50ba7 refactor: include build config in source files 2023-05-28 17:03:52 +02:00
Lukas Obermann b74219a78e build(release): 0.11.5 2023-05-28 00:17:32 +02:00
Lukas Obermann 955928c90c build(ts): only use main tsconfig.json for compiling 2023-05-28 00:17:22 +02:00
Lukas Obermann b41ff533df build(release): 0.11.4 2023-05-28 00:10:46 +02:00
Lukas Obermann 98f85f2f3b refactor: add missing type declarations and optimize with projects 2023-05-28 00:10:32 +02:00
Lukas Obermann 4d27ff6489 refactor: use node prefix in all node module imports 2023-05-28 00:08:50 +02:00
Lukas Obermann 04c0cd5299 build(npm): upgrade optolith-tsjsonschemamd to 0.8.0 as dev dependency 2023-05-28 00:07:52 +02:00
Lukas Obermann 9d7e74d79e refactor(git): delete unnecessary config.d.ts 2023-05-27 17:57:05 +02:00
Lukas Obermann 95ea12eade build(release): 0.11.3 2023-05-27 17:09:54 +02:00
Lukas Obermann 5aea22488a build(npm): include optolith-tsjsonschemamd as a runtime dependency 2023-05-27 17:09:45 +02:00
Lukas Obermann 35f0fb0ad4 build(npm): include build config 2023-05-27 17:09:20 +02:00
Lukas Obermann 78235f9410 revert: extract folder config
Ref: 2ceaa27c56
2023-05-27 17:08:55 +02:00
Lukas Obermann 3ada66e2ad build(release): 0.11.2 2023-05-27 16:56:48 +02:00
Lukas Obermann 2ceaa27c56 refactor: extract folder config 2023-05-27 16:56:35 +02:00
Lukas Obermann c016512654 build(release): 0.11.1 2023-05-27 16:42:29 +02:00
Lukas Obermann ebb123f6fd build: include folder config in npm package 2023-05-27 16:42:22 +02:00
Lukas Obermann 72cbdda67e build(release): 0.11.0 2023-05-27 16:03:00 +02:00
Lukas Obermann d39110ea32 feat!: add integrity validation capabilities 2023-05-27 16:02:39 +02:00
Lukas Obermann eb8f895393 ci: upgrade node 2023-05-27 16:02:23 +02:00
Lukas Obermann 771682abca build!: use exports field in package.json 2023-05-27 16:01:20 +02:00
Lukas Obermann 84ac40046e docs: improve references to project management 2023-05-16 23:17:43 +02:00
Lukas Obermann b48a751526 build(release): 0.10.1 2023-05-01 17:06:39 +02:00
Lukas Obermann e4bf0eda5d build(deps): update dependencies
This also fixes a security vulnerability in the `yaml` package.
2023-05-01 17:06:28 +02:00
Lukas Obermann 2337d66537 build(release): 0.10.0 2023-05-01 17:03:19 +02:00
Lukas Obermann 0ea6a838e2 feat!: finish schema 2023-05-01 17:02:53 +02:00
Lukas Obermann 230326ff25 fix: ignore hidden files 2023-01-24 20:08:02 +01:00
Lukas Obermann abc0bb13fd feat: sort errors by file path, with numbers in ascending order 2023-01-24 19:55:03 +01:00
Lukas Obermann 4f6b2aea2d fix: allow numbers in names 2023-01-24 19:54:30 +01:00
Lukas Obermann 8b1144e6f1 fix: the file name id may be 0 for custom entries 2023-01-24 19:02:51 +01:00
Lukas Obermann 50b2020bd6 fix: uppercase letters should be allowed after the first letter
This is required, since removing apostrophes may result in an uppercase letter
inside of a word.
2023-01-24 19:02:25 +01:00
Lukas Obermann 4179a81307 build(release): 0.9.0 2023-01-14 12:24:28 +01:00
Lukas Obermann 1a3f34a388 fix: wand enchantments dont use volume 2023-01-14 12:24:24 +01:00
Lukas Obermann a395778303 fix: translation was not embedded in locale map 2023-01-14 12:23:34 +01:00
Lukas Obermann f6671e31bd feat: add trinkhornzauber identifier to applying identifier groups 2023-01-14 00:05:56 +01:00
Lukas Obermann 60254855e2 fix: make cost optional for all tradition arfifact enchantments 2023-01-14 00:04:12 +01:00
Lukas Obermann 27640a6950 feat!: make protective/warding circle cost language-independent 2023-01-12 23:46:48 +01:00
Lukas Obermann 0e5bbd485d fix: advanced special ability OneOf option should allow and required multiple options 2023-01-11 23:55:10 +01:00
Lukas Obermann 687665d0a0 fix: some tradition properties have to be optional 2023-01-11 21:14:31 +01:00
Lukas Obermann 4517d64bda feat: magical signs 2023-01-09 12:02:56 +01:00
Lukas Obermann a8c45d148d feat!: change magical rune options to explicit options 2023-01-09 11:56:08 +01:00
Lukas Obermann 91b83d296c build(release): 0.8.1 2023-01-08 01:12:15 +01:00
Lukas Obermann 21289fe81d feat: allow to restrict advanced special ability to aspect option 2023-01-08 01:03:27 +01:00
Lukas Obermann 2ead215fc1 fix: optional language specializations 2023-01-05 15:42:45 +01:00
Lukas Obermann 448978f381 feat: allow minimum total for countable cost for sustained skills 2023-01-05 10:11:04 +01:00
Lukas Obermann 9be75fe937 feat: allow only casting time during lovemaking to be present 2023-01-03 17:32:58 +01:00
Lukas Obermann 5756ecd33f feat: use summoning and creation difficulty as possible skill check penalties 2023-01-03 16:42:43 +01:00
938 changed files with 54611 additions and 118038 deletions
+25 -20
View File
@@ -9,28 +9,33 @@ on:
- v[0-9]+.[0-9]+.[0-9]+*
jobs:
# build:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2
# with:
# node-version: 16
# - run: npm ci
# - run: npm test
publish-npm:
# needs: build
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Checkout repository
uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 16
node-version: 24
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build:ts
- run: npm run build:gen
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- name: Install dependencies
run: npm ci
- name: Build TypeScript (first pass)
run: npm run build
continue-on-error: true
- name: Generate Typings
run: npm run generate
- name: Build TypeScript (second pass)
run: npm run build
- name: Lint
run: npm run lint
- name: Check code formatting
run: npm run format:check
- name: Test schema validity
run: npm test
- name: Publish to npm
run: npm publish --provenance --access public
+44
View File
@@ -0,0 +1,44 @@
# 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:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [23, 24]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build TypeScript (first pass)
run: npm run build
continue-on-error: true
- name: Generate Typings
run: npm run generate
- name: Build TypeScript (second pass)
run: npm run build
- name: Lint
run: npm run lint
- name: Check code formatting
run: npm run format:check
- name: Test schema validity
run: npm test
+12
View File
@@ -1,2 +1,14 @@
gen
node_modules
lib
*.tsbuildinfo
# Swift
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
-8
View File
@@ -1,8 +0,0 @@
.github
.versionrc.json
.vscode
build
docs
ideas
src
tsconfig.json
+3
View File
@@ -0,0 +1,3 @@
semi: false
arrowParens: avoid
printWidth: 100
+3
View File
@@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
+2 -2
View File
@@ -6,7 +6,7 @@
{
"label": "watch ts build",
"type": "shell",
"command": "npm run watch:ts",
"command": "npm run watch",
"isBackground": true,
"group": "build",
"presentation": {
@@ -16,7 +16,7 @@
{
"label": "watch generation from ts",
"type": "shell",
"command": "npm run watch:gen",
"command": "npm run generate:watch",
"isBackground": true,
"group": "build",
"presentation": {
+1488 -1
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
# Contribute
## Prerequisites
You will need Node.js installed. Install dependencies via `npm install`/`npm i`.
## Building and build passes
Successful compilation requires some generated types, as these are used to provide type safety for unique constraints and other features. However, the project needs to be built first to be able to generate the types. Since TypeScript compiles even with errors, you will need to build first, then generate the types, and then build a second time with the generated types.
- **Build TypeScript:** `npm run build`
- **Generate Types:** `npm run generate`
Generated files will be saved to the `/gen` folder; compiled JavaScript files will be saved to the `/lib` folder.
### Watch
You can also watch for changes when you are working on the schema. For this, there are two commands you can run in parallel. You do not need to care about build passes in that case, as the watcher processes will take care of it.
- **Watch TypeScript:** `npm run watch`
- **Watch Generate Types:** `npm run generate:watch`
## Testing with the database
In order to test changes with the actual database, one approach could be to install the schema as a local npm package in the database. To do so, go to your local database copy and run `npm i ../path/to/schema`, where you just point to your local working copy of the database schema repository (the one this document belongs to).
Make sure to always restart the editor when you make changes, as the database schema is not automatically reloaded. If the editor UI does not update properly, try resetting the browser cache by holding shift while reloading the page.
## Naming conventions
If you create branches in this repository, use either `feature/<topic>` or `fix/<topic>` as the branch name and replace `<topic>` with a kebab case version of what the branch is going to do. Branches are merged using squash merge, so commits within a pull request do not have to follow any naming convention, just do what you can work with best.
Alternatively, you can fork the repository and create pull requests from there.
## New entities
If you add new entities, there are some additional steps to take.
1. Add the corresponding identifier to the `src/types/_Identifier.ts` file. Just duplicate an existing one, adjust the referenced entity at the end and make sure the variable/function name is the entity name plus `Identifier` and is inserted into the list of identifiers in alphabetical order.
2. Check `src/types/_IdentifierGroup.ts` if you need to add the entity to any existing identifier group. For example, if you add another special ability entity, add it to the `SpecialAbilityIdentifier` group; if it is a combat-related special ability, add it to the `CombatRelatedSpecialAbilityIdentifier` group as well.
3. **If it is not a child entity:** Export the entity declaration alone from `src/types/index.ts`.
4. **If it is not a child entity:** Add the entity declaration to the schema definition in `src/main.ts` in alphabetical order.
## Formatting and linting
This repository makes use of linting via ESLint and formatting via Prettier. It can be helpful to use available editor extensions when contributing to fix errors before they appear in your PR. Formatting and linting is checked for every pull request and must succeed in order to be able to accept your PR.
+24
View File
@@ -0,0 +1,24 @@
{
"originHash" : "8136f4350c7b746f8a6bfb9ad7b6473b656e39e2c5bf8fed118646234dbac7b0",
"pins" : [
{
"identity" : "discriminatedenum",
"kind" : "remoteSourceControl",
"location" : "https://github.com/elyukai/DiscriminatedEnum.git",
"state" : {
"branch" : "main",
"revision" : "0043d48d48f9880cb18198e173c479f27040b901"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax.git",
"state" : {
"revision" : "0687f71944021d616d34d922343dcef086855920",
"version" : "600.0.1"
}
}
],
"version" : 3
}
+37
View File
@@ -0,0 +1,37 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
import CompilerPluginSupport
let package = Package(
name: "OptolithDatabaseSchema",
platforms: [
.macOS(.v13), .iOS(.v16), .tvOS(.v16), .watchOS(.v9), .macCatalyst(.v16)
],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "OptolithDatabaseSchema",
targets: ["OptolithDatabaseSchema"]),
],
dependencies: [
.package(url: "https://github.com/elyukai/DiscriminatedEnum.git", branch: "main"),
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "OptolithDatabaseSchema",
dependencies: ["DiscriminatedEnum", "OptolithDatabaseSchemaMacros"]),
.macro(
name: "OptolithDatabaseSchemaMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
]
)
+18 -1
View File
@@ -10,8 +10,25 @@ The entity reference in the documentation and the JSON Schema documents are gene
If you want to contribute, it is most likely you want to help with inserting new data into the database. In this case, the [User Documentation](docs/Home.md) is the place to start. Conversations usually happen on the [Optolith Discord Server](https://discord.gg/wfdgB9g). We can also manage required permissions there, since you can't publicly access the database, as already mentioned.
If you want to contribute to the database schema itself, please open an issue or discussion (whatever fits best) with what you want to achieve so we can discuss it. Breaking changes would require changes inside the database as well, so Pull Requests are usually not the way to go unless you want to fix a bug.
If you want to contribute to the database schema itself, please open an issue or discussion (whatever fits best) with what you want to achieve so we can discuss it. Please look at the [CONTRIBUTING](./CONTRIBUTING.md) document for actual contributing to the schema repository.
## Usage
If you want to use the database (or parts of it) in your own project (most likely to support Optolith export formats in your own application), you can use the TypeScript sources as a simple database reader, at least if you have a project targeting JavaScript interpreters as well. This way, you already have proper typings. You can extract the database from the installation folder of Optolith and then configure the database reader to your needs.
## Semantic Versioning
The following situations are considered breaking changes:
- Underlying data must be changed in order to comply with the new schema. Examples:
- Existing optional properties are made required
- New required properties are added
- Properties or enumeration cases are removed
- Code that uses the data must be adjusted in order to use the new version. Examples:
- Existing required properties are made optional
- Enumeration cases are added
- Properties or enumeration cases are removed
This implies that breaking changes can occur quite frequently, even if the changes are very small.
A minor version bump can represent new optional properties, while patch version might represent adjustments in comments or display names for the editor.
@@ -0,0 +1,23 @@
//
// Entity.swift
// OptolithDatabaseSchema
//
// Created by Lukas Obermann on 15.11.24.
//
/// A type whose data is represented by a single file in the database.
public protocol Entity: Decodable, Hashable, Sendable { }
/// An entity that has associated translations for display purposes.
public protocol LocalizableEntity: Entity {
associatedtype Translation: EntitySubtype
var translations: LocaleMap<Translation> { get }
}
/// A subtype of an entity type that provides conformance to protocols an entity type must conform to. It is used purely for convenience purposes.
public protocol EntitySubtype: Decodable, Hashable, Sendable { }
extension String: EntitySubtype { }
extension Double: EntitySubtype { }
extension Int: EntitySubtype { }
@@ -0,0 +1,106 @@
//
// Advantage.swift
// OptolithDatabaseSchema
//
public struct Advantage: LocalizableEntity {
public let id: Id
public let levels: Levels?
public let selectOptions: SelectOptions?
public let skillApplications: SkillApplications?
public let skillUses: SkillUses?
public let maximum: Maximum?
public let prerequisites: AdvantageDisadvantagePrerequisites?
public let apValue: AdventurePointsValue
/// Does this advantage count towards the maximum of AP to be spent on advantages?
public let hasMaximumSpentInfluence: Bool
/// Does this advantage exclusively applies to arcane spellworks and not to magical actions and magical applications?
public let isExclusiveToArcaneSpellworks: Bool
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<AdvantageTranslation>
public init(id: Id, levels: Levels? = nil, selectOptions: SelectOptions? = nil, skillApplications: SkillApplications? = nil, skillUses: SkillUses? = nil, maximum: Maximum? = nil, prerequisites: AdvantageDisadvantagePrerequisites? = nil, apValue: AdventurePointsValue, hasMaximumSpentInfluence: Bool, isExclusiveToArcaneSpellworks: Bool, src: PublicationRefs, translations: LocaleMap<AdvantageTranslation>) {
self.id = id
self.levels = levels
self.selectOptions = selectOptions
self.skillApplications = skillApplications
self.skillUses = skillUses
self.maximum = maximum
self.prerequisites = prerequisites
self.apValue = apValue
self.hasMaximumSpentInfluence = hasMaximumSpentInfluence
self.isExclusiveToArcaneSpellworks = isExclusiveToArcaneSpellworks
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case levels = "levels"
case selectOptions = "select_options"
case skillApplications = "skill_applications"
case skillUses = "skill_uses"
case maximum = "maximum"
case prerequisites = "prerequisites"
case apValue = "ap_value"
case hasMaximumSpentInfluence = "has_maximum_spent_influence"
case isExclusiveToArcaneSpellworks = "is_exclusive_to_arcane_spellworks"
case src = "src"
case translations = "translations"
}
}
public struct AdvantageTranslation: EntitySubtype {
public let name: Name
public let nameInLibrary: NameInLibrary?
/// A string that is used as a label for an input field.
public let input: Input?
public let rules: Rules
/// The range.
public let range: String?
public let apValue: AdventurePointsValueReplacement?
/// A string that gets appended to the default AP Value text with a preceding space. This always happens if present, even if the generated AP Value text is replaced.
public let apValueAppend: AdventurePointsValueAppend?
public let errata: Errata?
public init(name: Name, nameInLibrary: NameInLibrary? = nil, input: Input? = nil, rules: Rules, range: String? = nil, apValue: AdventurePointsValueReplacement? = nil, apValueAppend: AdventurePointsValueAppend? = nil, errata: Errata? = nil) {
self.name = name
self.nameInLibrary = nameInLibrary
self.input = input
self.rules = rules
self.range = range
self.apValue = apValue
self.apValueAppend = apValueAppend
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case nameInLibrary = "name_in_library"
case input = "input"
case rules = "rules"
case range = "range"
case apValue = "ap_value"
case apValueAppend = "ap_value_append"
case errata = "errata"
}
}
@@ -0,0 +1,55 @@
//
// AnimalDisease.swift
// OptolithDatabaseSchema
//
public struct AnimalDisease: LocalizableEntity {
/// The animal disease's identifier. An unique, increasing integer.
public let id: Int
/// The animal disease's level.
public let level: Int
/// Depending on the disease, apply Spirit or Toughness as a penalty to the disease roll. It may also happen that the lower of both is applied as a penalty.
public let resistance: Resistance
/// What causes the disease? The GM rolls 1D20 to see if a character gets infected. If the infection check succeeds, the GM makes a disease check to determine the severity of the infection.
public let cause: [Cause]
/// The animal types this disease applies to.
///
/// If no animal types are given, the animal disease applies to all animal types.
public let animalTypes: [AnimalTypeReference]
/// If and at which chance the disease can be communicated to intelligent creatures.
///
/// If no causes are given, the disease is not communicable to intelligent creatures.
public let communicabilityToIntelligentCreatures: [Cause]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DiseaseTranslation>
public init(id: Int, level: Int, resistance: Resistance, cause: [Cause], animalTypes: [AnimalTypeReference], communicabilityToIntelligentCreatures: [Cause], src: PublicationRefs, translations: LocaleMap<DiseaseTranslation>) {
self.id = id
self.level = level
self.resistance = resistance
self.cause = cause
self.animalTypes = animalTypes
self.communicabilityToIntelligentCreatures = communicabilityToIntelligentCreatures
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case level = "level"
case resistance = "resistance"
case cause = "cause"
case animalTypes = "animal_types"
case communicabilityToIntelligentCreatures = "communicability_to_intelligent_creatures"
case src = "src"
case translations = "translations"
}
}
@@ -0,0 +1,26 @@
//
// AnimalType.swift
// OptolithDatabaseSchema
//
public struct AnimalType: LocalizableEntity {
/// The animal type's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<AnimalTypeTranslation>
public init(id: Int, translations: LocaleMap<AnimalTypeTranslation>) {
self.id = id
self.translations = translations
}
}
public struct AnimalTypeTranslation: EntitySubtype {
/// The animal type name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,6 @@
//
// ArcaneBardTradition.swift
// OptolithDatabaseSchema
//
public typealias ArcaneBardTradition = ArcaneTradition
@@ -0,0 +1,6 @@
//
// ArcaneDancerTradition.swift
// OptolithDatabaseSchema
//
public typealias ArcaneDancerTradition = ArcaneTradition
@@ -0,0 +1,35 @@
//
// Aspect.swift
// OptolithDatabaseSchema
//
public struct Aspect: LocalizableEntity {
/// The aspect's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<AspectTranslation>
public init(id: Int, translations: LocaleMap<AspectTranslation>) {
self.id = id
self.translations = translations
}
}
public struct AspectTranslation: EntitySubtype {
/// The aspect name.
public let name: NonEmptyString
/// The aspect's name appended to the simple name (not `name_in_library`) of the special ability *Master of (Aspect)*.
public let masterOfAspectSuffix: NonEmptyString?
public init(name: NonEmptyString, masterOfAspectSuffix: NonEmptyString? = nil) {
self.name = name
self.masterOfAspectSuffix = masterOfAspectSuffix
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case masterOfAspectSuffix = "master_of_aspect_suffix"
}
}
@@ -0,0 +1,34 @@
//
// Attribute.swift
// OptolithDatabaseSchema
//
public struct Attribute: LocalizableEntity {
/// The attribute's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<AttributeTranslation>
public init(id: Int, translations: LocaleMap<AttributeTranslation>) {
self.id = id
self.translations = translations
}
}
public struct AttributeTranslation: EntitySubtype {
/// The attribute's name.
public let name: NonEmptyString
/// The abbreviation of the attribute's name.
public let abbreviation: NonEmptyString
/// The description of the attribute.
public let description: NonEmptyString
public init(name: NonEmptyString, abbreviation: NonEmptyString, description: NonEmptyString) {
self.name = name
self.abbreviation = abbreviation
self.description = description
}
}
@@ -0,0 +1,180 @@
//
// Blessing.swift
// OptolithDatabaseSchema
//
public struct Blessing: LocalizableEntity {
/// The blessing's identifier. An unique, increasing integer.
public let id: Int
/// Measurable parameters of a blessing.
public let parameters: BlessingPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<BlessingTranslation>
public init(id: Int, parameters: BlessingPerformanceParameters, `target`: AffectedTargetCategories, src: PublicationRefs, translations: LocaleMap<BlessingTranslation>) {
self.id = id
self.parameters = parameters
self.`target` = `target`
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case parameters = "parameters"
case `target` = "target"
case src = "src"
case translations = "translations"
}
}
public struct BlessingTranslation: EntitySubtype {
/// The name of the blessing.
public let name: String
/// The effect description.
public let effect: String
@available(*, deprecated)
public let range: String
@available(*, deprecated)
public let duration: String
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: String, effect: String, range: String, duration: String, `target`: String, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case effect = "effect"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
/// Measurable parameters of a blessing.
public struct BlessingPerformanceParameters: EntitySubtype {
public let range: BlessingRange
public let duration: BlessingDuration
public init(range: BlessingRange, duration: BlessingDuration) {
self.range = range
self.duration = duration
}
}
public enum BlessingRange: EntitySubtype {
case `self`
case touch
case fixed(FixedRange)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case `self` = "self"
case touch = "touch"
case fixed = "fixed"
}
private enum Discriminator: String, Decodable {
case `self` = "Self"
case touch = "Touch"
case fixed = "Fixed"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .`self`:
self = .`self`
case .touch:
self = .touch
case .fixed:
self = .fixed(try container.decode(FixedRange.self, forKey: .fixed))
}
}
}
public enum BlessingDuration: EntitySubtype {
case immediate
case fixed(FixedBlessingDuration)
case indefinite(IndefiniteBlessingDuration)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case immediate = "immediate"
case fixed = "fixed"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case immediate = "Immediate"
case fixed = "Fixed"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .immediate:
self = .immediate
case .fixed:
self = .fixed(try container.decode(FixedBlessingDuration.self, forKey: .fixed))
case .indefinite:
self = .indefinite(try container.decode(IndefiniteBlessingDuration.self, forKey: .indefinite))
}
}
}
public struct FixedBlessingDuration: EntitySubtype {
/// The (unitless) duration.
public let value: Int
/// The duration unit.
public let unit: DurationUnit
public init(value: Int, unit: DurationUnit) {
self.value = value
self.unit = unit
}
}
public struct IndefiniteBlessingDuration: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<IndefiniteBlessingDurationTranslation>
public init(translations: LocaleMap<IndefiniteBlessingDurationTranslation>) {
self.translations = translations
}
}
public struct IndefiniteBlessingDurationTranslation: EntitySubtype {
/// A description of the duration.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
@@ -0,0 +1,303 @@
//
// Cantrip.swift
// OptolithDatabaseSchema
//
public struct Cantrip: LocalizableEntity {
/// The cantrip's identifier. An unique, increasing integer.
public let id: Int
/// Measurable parameters of a cantrip.
public let parameters: CantripPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
/// The associated property.
public let property: PropertyReference
/// A note specifying the dissemination of the cantrip in different traditions. Sometimes a cantrip is exclusively available to one or more specific traditions, but usually one the academies and traditions are listed the cantrip is most commonly teached in.
public let note: CantripNote?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CantripTranslation>
public let enhancements: Enhancements?
public init(id: Int, parameters: CantripPerformanceParameters, `target`: AffectedTargetCategories, property: PropertyReference, note: CantripNote? = nil, src: PublicationRefs, translations: LocaleMap<CantripTranslation>, enhancements: Enhancements? = nil) {
self.id = id
self.parameters = parameters
self.`target` = `target`
self.property = property
self.note = note
self.src = src
self.translations = translations
self.enhancements = enhancements
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case parameters = "parameters"
case `target` = "target"
case property = "property"
case note = "note"
case src = "src"
case translations = "translations"
case enhancements = "enhancements"
}
}
public enum CantripNote: EntitySubtype {
case exclusive(ExclusiveCantripNote)
case common(CommonCantripNotes)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case exclusive = "exclusive"
case common = "common"
}
private enum Discriminator: String, Decodable {
case exclusive = "Exclusive"
case common = "Common"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .exclusive:
self = .exclusive(try container.decode(ExclusiveCantripNote.self, forKey: .exclusive))
case .common:
self = .common(try container.decode(CommonCantripNotes.self, forKey: .common))
}
}
}
public struct ExclusiveCantripNote: EntitySubtype {
/// The traditions the cantrip is exclusively available to.
public let traditions: [MagicalTraditionReference]
public init(traditions: [MagicalTraditionReference]) {
self.traditions = traditions
}
}
public struct CommonCantripNotes: EntitySubtype {
/// The academies and traditions the cantrip is commonly teached in.
public let list: [CommonCantripNote]
public init(list: [CommonCantripNote]) {
self.list = list
}
}
public enum CommonCantripNote: EntitySubtype {
case academy(CurriculumReference)
case tradition(CommonCantripTraditionNote)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case academy = "academy"
case tradition = "tradition"
}
private enum Discriminator: String, Decodable {
case academy = "Academy"
case tradition = "Tradition"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .academy:
self = .academy(try container.decode(CurriculumReference.self, forKey: .academy))
case .tradition:
self = .tradition(try container.decode(CommonCantripTraditionNote.self, forKey: .tradition))
}
}
}
public struct CommonCantripTraditionNote: EntitySubtype {
/// The magical tradition's identifier.
public let id: MagicalTraditionIdentifier
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CommonCantripTraditionNoteTranslation>?
public init(id: MagicalTraditionIdentifier, translations: LocaleMap<CommonCantripTraditionNoteTranslation>? = nil) {
self.id = id
self.translations = translations
}
}
public struct CommonCantripTraditionNoteTranslation: EntitySubtype {
/// A note, appended to the generated string in parenthesis.
public let note: String
public init(note: String) {
self.note = note
}
}
public struct CantripTranslation: EntitySubtype {
/// The name of the spell.
public let name: String
/// The effect description.
public let effect: String
@available(*, deprecated)
public let range: String
@available(*, deprecated)
public let duration: String
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: String, effect: String, range: String, duration: String, `target`: String, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case effect = "effect"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
/// Measurable parameters of a blessing.
public struct CantripPerformanceParameters: EntitySubtype {
public let range: CantripRange
public let duration: CantripDuration
public init(range: CantripRange, duration: CantripDuration) {
self.range = range
self.duration = duration
}
}
public enum CantripRange: EntitySubtype {
case `self`
case touch
case fixed(FixedRange)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case `self` = "self"
case touch = "touch"
case fixed = "fixed"
}
private enum Discriminator: String, Decodable {
case `self` = "Self"
case touch = "Touch"
case fixed = "Fixed"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .`self`:
self = .`self`
case .touch:
self = .touch
case .fixed:
self = .fixed(try container.decode(FixedRange.self, forKey: .fixed))
}
}
}
public enum CantripDuration: EntitySubtype {
case immediate
case fixed(FixedCantripDuration)
case duringLovemaking(CastingTimeDuringLovemaking)
case indefinite(IndefiniteCantripDuration)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case immediate = "immediate"
case fixed = "fixed"
case duringLovemaking = "during_lovemaking"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case immediate = "Immediate"
case fixed = "Fixed"
case duringLovemaking = "DuringLovemaking"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .immediate:
self = .immediate
case .fixed:
self = .fixed(try container.decode(FixedCantripDuration.self, forKey: .fixed))
case .duringLovemaking:
self = .duringLovemaking(try container.decode(CastingTimeDuringLovemaking.self, forKey: .duringLovemaking))
case .indefinite:
self = .indefinite(try container.decode(IndefiniteCantripDuration.self, forKey: .indefinite))
}
}
}
public struct FixedCantripDuration: EntitySubtype {
/// If `true`, the duration is a maximum duration.
public let isMaximum: Bool?
/// The (unitless) duration.
public let value: Int
/// The duration unit.
public let unit: DurationUnit
public init(isMaximum: Bool? = nil, value: Int, unit: DurationUnit) {
self.isMaximum = isMaximum
self.value = value
self.unit = unit
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case value = "value"
case unit = "unit"
}
}
public struct IndefiniteCantripDuration: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<IndefiniteCantripDurationTranslation>
public init(translations: LocaleMap<IndefiniteCantripDurationTranslation>) {
self.translations = translations
}
}
public struct IndefiniteCantripDurationTranslation: EntitySubtype {
/// A description of the duration.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
@@ -0,0 +1,116 @@
//
// Ceremony.swift
// OptolithDatabaseSchema
//
public struct Ceremony: LocalizableEntity {
/// The ceremony's identifier. An unique, increasing integer.
public let id: Int
/// Lists the linked three attributes used to make a skill check.
public let check: SkillCheck
/// In some cases, the target's Spirit or Toughness is applied as a penalty.
public let checkPenalty: SkillCheckPenalty?
/// Measurable parameters of a ceremony.
public let parameters: SlowPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
/// The tradition(s) the ceremony is available for. Note that general aspects do not have an associated tradition and thus need to be defined in a special way.
public let traditions: [SkillTradition]
/// States which column is used to improve the skill.
public let improvementCost: ImprovementCost
public let prerequisites: LiturgyPrerequisites?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CeremonyTranslation>
public let enhancements: Enhancements?
public init(id: Int, check: SkillCheck, checkPenalty: SkillCheckPenalty? = nil, parameters: SlowPerformanceParameters, `target`: AffectedTargetCategories, traditions: [SkillTradition], improvementCost: ImprovementCost, prerequisites: LiturgyPrerequisites? = nil, src: PublicationRefs, translations: LocaleMap<CeremonyTranslation>, enhancements: Enhancements? = nil) {
self.id = id
self.check = check
self.checkPenalty = checkPenalty
self.parameters = parameters
self.`target` = `target`
self.traditions = traditions
self.improvementCost = improvementCost
self.prerequisites = prerequisites
self.src = src
self.translations = translations
self.enhancements = enhancements
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case check = "check"
case checkPenalty = "check_penalty"
case parameters = "parameters"
case `target` = "target"
case traditions = "traditions"
case improvementCost = "improvement_cost"
case prerequisites = "prerequisites"
case src = "src"
case translations = "translations"
case enhancements = "enhancements"
}
}
public struct CeremonyTranslation: EntitySubtype {
/// The name of the ceremony.
public let name: NonEmptyString
/// A compressed name of the ceremony for use in small areas (e.g. on character sheet). Should only be defined if the `name` does not fit on character sheet.
public let nameCompressed: NonEmptyString?
/// The effect description may be either a plain text or a text that is divided by a list of effects for each quality level. It may also be a list for each two quality levels.
public let effect: ActivatableSkillEffect
@available(*, deprecated)
public let castingTime: OldParameter
@available(*, deprecated)
public let cost: OldParameter
@available(*, deprecated)
public let range: OldParameter
@available(*, deprecated)
public let duration: OldParameter
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: NonEmptyString, nameCompressed: NonEmptyString? = nil, effect: ActivatableSkillEffect, castingTime: OldParameter, cost: OldParameter, range: OldParameter, duration: OldParameter, `target`: String, errata: Errata? = nil) {
self.name = name
self.nameCompressed = nameCompressed
self.effect = effect
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case nameCompressed = "name_compressed"
case effect = "effect"
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
@@ -0,0 +1,91 @@
//
// CombatTechnique_Close.swift
// OptolithDatabaseSchema
//
public struct CloseCombatTechnique: LocalizableEntity {
/// The close combat technique's identifier. An unique, increasing integer.
public let id: Int
/// Special rules for the combat technique that apply to all weapons in this category.
public let special: CloseCombatTechniqueSpecialRules
/// The primary attribute(s).
public let primaryAttribute: [AttributeReference]
/// The *Breaking Point Rating* of the respective combat technique.
public let breakingPointRating: Int
public let improvementCost: ImprovementCost
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CloseCombatTechniqueTranslation>
public init(id: Int, special: CloseCombatTechniqueSpecialRules, primaryAttribute: [AttributeReference], breakingPointRating: Int, improvementCost: ImprovementCost, src: PublicationRefs, translations: LocaleMap<CloseCombatTechniqueTranslation>) {
self.id = id
self.special = special
self.primaryAttribute = primaryAttribute
self.breakingPointRating = breakingPointRating
self.improvementCost = improvementCost
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case special = "special"
case primaryAttribute = "primary_attribute"
case breakingPointRating = "breaking_point_rating"
case improvementCost = "improvement_cost"
case src = "src"
case translations = "translations"
}
}
/// Special rules for the combat technique that apply to all weapons in this category.
public struct CloseCombatTechniqueSpecialRules: EntitySubtype {
/// Is parrying possible with this combat technique?
public let canParry: Bool
public let hasDamageThreshold: Bool
public let hasReach: Bool
public let hasLength: Bool
public let hasShieldSize: Bool
public init(canParry: Bool, hasDamageThreshold: Bool, hasReach: Bool, hasLength: Bool, hasShieldSize: Bool) {
self.canParry = canParry
self.hasDamageThreshold = hasDamageThreshold
self.hasReach = hasReach
self.hasLength = hasLength
self.hasShieldSize = hasShieldSize
}
private enum CodingKeys: String, CodingKey {
case canParry = "can_parry"
case hasDamageThreshold = "has_damage_threshold"
case hasReach = "has_reach"
case hasLength = "has_length"
case hasShieldSize = "has_shield_size"
}
}
public struct CloseCombatTechniqueTranslation: EntitySubtype {
/// The name of the condition.
public let name: String
/// Additional rules for the condition, if applicable.
public let special: String?
public let errata: Errata?
public init(name: String, special: String? = nil, errata: Errata? = nil) {
self.name = name
self.special = special
self.errata = errata
}
}
@@ -0,0 +1,74 @@
//
// CombatTechnique_Ranged.swift
// OptolithDatabaseSchema
//
public struct RangedCombatTechnique: LocalizableEntity {
/// The ranged combat technique's identifier. An unique, increasing integer.
public let id: Int
/// Special rules for the combat technique that apply to all weapons in this category.
public let special: RangedCombatTechniqueSpecialRules
/// The primary attribute(s).
public let primaryAttribute: [AttributeReference]
/// The *Breaking Point Rating* of the respective combat technique.
public let breakingPointRating: Int
public let improvementCost: ImprovementCost
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RangedCombatTechniqueTranslation>
public init(id: Int, special: RangedCombatTechniqueSpecialRules, primaryAttribute: [AttributeReference], breakingPointRating: Int, improvementCost: ImprovementCost, src: PublicationRefs, translations: LocaleMap<RangedCombatTechniqueTranslation>) {
self.id = id
self.special = special
self.primaryAttribute = primaryAttribute
self.breakingPointRating = breakingPointRating
self.improvementCost = improvementCost
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case special = "special"
case primaryAttribute = "primary_attribute"
case breakingPointRating = "breaking_point_rating"
case improvementCost = "improvement_cost"
case src = "src"
case translations = "translations"
}
}
/// Special rules for the combat technique that apply to all weapons in this category.
public struct RangedCombatTechniqueSpecialRules: EntitySubtype {
public let hasAmmunition: Bool
public init(hasAmmunition: Bool) {
self.hasAmmunition = hasAmmunition
}
private enum CodingKeys: String, CodingKey {
case hasAmmunition = "has_ammunition"
}
}
public struct RangedCombatTechniqueTranslation: EntitySubtype {
/// The name of the condition.
public let name: String
/// Additional rules for the condition, if applicable.
public let special: String?
public let errata: Errata?
public init(name: String, special: String? = nil, errata: Errata? = nil) {
self.name = name
self.special = special
self.errata = errata
}
}
@@ -0,0 +1,40 @@
//
// Condition.swift
// OptolithDatabaseSchema
//
public struct Condition: LocalizableEntity {
/// The condition's identifier. An unique, increasing integer.
public let id: Int
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ConditionTranslation>
public init(id: Int, src: PublicationRefs, translations: LocaleMap<ConditionTranslation>) {
self.id = id
self.src = src
self.translations = translations
}
}
public struct ConditionTranslation: EntitySubtype {
/// The condition's name.
public let name: NonEmptyString
/// Additional rules for the condition, if applicable.
public let rules: NonEmptyMarkdown?
/// The effects for level 1 to 4.
public let effects: [NonEmptyMarkdown]
public let errata: Errata?
public init(name: NonEmptyString, rules: NonEmptyMarkdown? = nil, effects: [NonEmptyMarkdown], errata: Errata? = nil) {
self.name = name
self.rules = rules
self.effects = effects
self.errata = errata
}
}
@@ -0,0 +1,27 @@
//
// Continent.swift
// OptolithDatabaseSchema
//
/// Continents are mostly referenced to in languages and scripts that occur on a specific continent.
public struct Continent: LocalizableEntity {
/// The continent's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ContinentTranslation>
public init(id: Int, translations: LocaleMap<ContinentTranslation>) {
self.id = id
self.translations = translations
}
}
public struct ContinentTranslation: EntitySubtype {
/// The continent name.
public let name: String
public init(name: String) {
self.name = name
}
}
@@ -0,0 +1,480 @@
//
// Culture.swift
// OptolithDatabaseSchema
//
public struct Culture: LocalizableEntity {
/// An unique, increasing integer.
public let id: Int
/// A list of native languages (usually it is only one).
public let language: [LanguageReference]
/// A list of native scripts (usually it is only one). If the culture does not use any script, leave this field empty.
public let script: [ScriptReference]?
/// If the area knowledge has a fixed value or can be adjusted.
public let areaKnowledge: AreaKnowledge
/// A list of possible social status in the respective culture.
public let socialStatus: [SocialStatusReference]
/// A list of professions that are typical for the culture, as well as professions that are rarely practiced or encountered in the culture. The list is either defined by group (as multiple lists) or plain (as a single list).
public let commonProfessions: CommonProfessions
/// A list of common advantages.
public let commonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// A list of common disadvantages.
public let commonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// A list of uncommon advantages.
public let uncommonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// A list of uncommon disadvantages.
public let uncommonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// A list of common skills.
public let commonSkills: [CommonnessRatedSkill]
/// A list of uncommon skills.
public let uncommonSkills: [CommonnessRatedSkill]?
/// The skill points you get for buying the culture package.
public let culturalPackage: [CulturalPackageItem]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CultureTranslation>
public init(id: Int, language: [LanguageReference], script: [ScriptReference]? = nil, areaKnowledge: AreaKnowledge, socialStatus: [SocialStatusReference], commonProfessions: CommonProfessions, commonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, commonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, uncommonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, uncommonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, commonSkills: [CommonnessRatedSkill], uncommonSkills: [CommonnessRatedSkill]? = nil, culturalPackage: [CulturalPackageItem], src: PublicationRefs, translations: LocaleMap<CultureTranslation>) {
self.id = id
self.language = language
self.script = script
self.areaKnowledge = areaKnowledge
self.socialStatus = socialStatus
self.commonProfessions = commonProfessions
self.commonAdvantages = commonAdvantages
self.commonDisadvantages = commonDisadvantages
self.uncommonAdvantages = uncommonAdvantages
self.uncommonDisadvantages = uncommonDisadvantages
self.commonSkills = commonSkills
self.uncommonSkills = uncommonSkills
self.culturalPackage = culturalPackage
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case language = "language"
case script = "script"
case areaKnowledge = "area_knowledge"
case socialStatus = "social_status"
case commonProfessions = "common_professions"
case commonAdvantages = "common_advantages"
case commonDisadvantages = "common_disadvantages"
case uncommonAdvantages = "uncommon_advantages"
case uncommonDisadvantages = "uncommon_disadvantages"
case commonSkills = "common_skills"
case uncommonSkills = "uncommon_skills"
case culturalPackage = "cultural_package"
case src = "src"
case translations = "translations"
}
}
/// If the area knowledge has a fixed value or can be adjusted.
public struct AreaKnowledge: EntitySubtype {
/// `true` if the area knowledge has a fixed value, `false` if it can be
/// adjusted.
public let isFixed: Bool
public init(isFixed: Bool) {
self.isFixed = isFixed
}
private enum CodingKeys: String, CodingKey {
case isFixed = "is_fixed"
}
}
/// The "weight" difference compared to other professions or profession variants. Some professions or profession variants are simply more common (Mostly), but sometimes only specific elements are used (Only).
public enum CommonnessWeight: String, EntitySubtype {
case mostly = "Mostly"
case only = "Only"
}
/// Some professions or profession variants are more common than others. There may be cultures where some professions or profession variants are not represented at all.
public struct Weighted<ProfessionOrVariant: EntitySubtype>: EntitySubtype {
/// The list of more common professions or profession variants.
public let elements: [ProfessionOrVariant]
/// The "weight" difference compared to other professions or profession variants. Some professions or profession variants are simply more common
/// (Mostly), but sometimes only specific elements are used (Only).
public let weight: CommonnessWeight
public init(elements: [ProfessionOrVariant], weight: CommonnessWeight) {
self.elements = elements
self.weight = weight
}
}
/// This defines how the list of constraints should be offset against the list of all mundane professions: Either only the professions are kept that intersect with the constraints (include) or only the professions are kept that are different from the constraints (exclude).
public enum CommonProfessionConstraintsOperation: String, EntitySubtype {
case intersection = "Intersection"
case difference = "Difference"
}
/// A list of professions. The filter specifies how the list is applied to all mundane professions.
public struct CommonProfessionConstraints<Constraint: EntitySubtype>: EntitySubtype {
/// The list of constraints.
public let constraints: [Constraint]
/// This defines how the list of constraints should be offset against the list of all mundane professions: Either only the professions are kept that intersect with the constraints (include) or only the professions are kept that are different from the constraints (exclude).
public let operation: CommonProfessionConstraintsOperation
public init(constraints: [Constraint], operation: CommonProfessionConstraintsOperation) {
self.constraints = constraints
self.operation = operation
}
}
/// Some professions may be found in a culture, but are not that common.
public enum Rarity: String, EntitySubtype {
case rare = "Rare"
case veryRare = "VeryRare"
}
public struct ProfessionConstraint: EntitySubtype {
/// The profession's identifier.
public let id: ProfessionIdentifier
/// Some profession variants are more common than others. There may be cultures where some variants are not represented at all.
public let weightedVariants: Weighted<ProfessionVariantReference>?
/// Some professions may be found in a culture, but are not that common.
public let rarity: Rarity?
public init(id: ProfessionIdentifier, weightedVariants: Weighted<ProfessionVariantReference>? = nil, rarity: Rarity? = nil) {
self.id = id
self.weightedVariants = weightedVariants
self.rarity = rarity
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case weightedVariants = "weighted_variants"
case rarity = "rarity"
}
}
/// Some professions may be found in a culture, but are not that common.
public enum MundaneProfessionSubgroupConstraint: String, EntitySubtype {
case profane = "Profane"
case fighter = "Fighter"
case religious = "Religious"
}
public struct MagicalTraditionConstraint: EntitySubtype {
/// The magical tradition's identifier.
public let id: MagicalTraditionIdentifier
/// Some professions are more common than others. There may be cultures where some professions are not represented at all.
public let weightedProfessions: Weighted<ProfessionReference>?
/// Some traditions may be found in a culture, but are not that common.
public let rarity: Rarity?
public init(id: MagicalTraditionIdentifier, weightedProfessions: Weighted<ProfessionReference>? = nil, rarity: Rarity? = nil) {
self.id = id
self.weightedProfessions = weightedProfessions
self.rarity = rarity
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case weightedProfessions = "weighted_professions"
case rarity = "rarity"
}
}
public struct BlessedTraditionConstraint: EntitySubtype {
/// The magical tradition's identifier.
public let id: BlessedTraditionIdentifier
/// Some professions are more common than others. There may be cultures where some professions are not represented at all.
public let weightedProfessions: Weighted<ProfessionReference>?
/// Some traditions may be found in a culture, but are not that common.
public let rarity: Rarity?
public init(id: BlessedTraditionIdentifier, weightedProfessions: Weighted<ProfessionReference>? = nil, rarity: Rarity? = nil) {
self.id = id
self.weightedProfessions = weightedProfessions
self.rarity = rarity
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case weightedProfessions = "weighted_professions"
case rarity = "rarity"
}
}
public enum MundaneCommonProfessionConstraint: EntitySubtype {
case profession(ProfessionConstraint)
case professionSubgroup(MundaneProfessionSubgroupConstraint)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case profession = "profession"
case professionSubgroup = "profession_subgroup"
}
private enum Discriminator: String, Decodable {
case profession = "Profession"
case professionSubgroup = "ProfessionSubgroup"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .profession:
self = .profession(try container.decode(ProfessionConstraint.self, forKey: .profession))
case .professionSubgroup:
self = .professionSubgroup(try container.decode(MundaneProfessionSubgroupConstraint.self, forKey: .professionSubgroup))
}
}
}
public enum MagicCommonProfessionConstraint: EntitySubtype {
case tradition(MagicalTraditionConstraint)
case magicDilettante
case profession(ProfessionConstraint)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case tradition = "tradition"
case magicDilettante = "magic_dilettante"
case profession = "profession"
}
private enum Discriminator: String, Decodable {
case tradition = "Tradition"
case magicDilettante = "MagicDilettante"
case profession = "Profession"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .tradition:
self = .tradition(try container.decode(MagicalTraditionConstraint.self, forKey: .tradition))
case .magicDilettante:
self = .magicDilettante
case .profession:
self = .profession(try container.decode(ProfessionConstraint.self, forKey: .profession))
}
}
}
public struct BlessedCommonProfessionConstraint: EntitySubtype {
public let tag: String
public let tradition: BlessedTraditionConstraint
public init(tag: String, tradition: BlessedTraditionConstraint) {
self.tag = tag
self.tradition = tradition
}
}
public typealias PlainCommonProfessions = CommonProfessionConstraints<ProfessionReference>
/// Lists of professions by group.
public struct GroupedCommonProfessions: EntitySubtype {
public let mundane: CommonProfessionConstraints<MundaneCommonProfessionConstraint>?
public let magic: CommonProfessionConstraints<MagicCommonProfessionConstraint>?
public let blessed: CommonProfessionConstraints<BlessedCommonProfessionConstraint>?
public init(mundane: CommonProfessionConstraints<MundaneCommonProfessionConstraint>? = nil, magic: CommonProfessionConstraints<MagicCommonProfessionConstraint>? = nil, blessed: CommonProfessionConstraints<BlessedCommonProfessionConstraint>? = nil) {
self.mundane = mundane
self.magic = magic
self.blessed = blessed
}
}
/// A list of professions that are typical for the culture, as well as professions that are rarely practiced or encountered in the culture. The list is either defined by group (as multiple lists) or plain (as a single list).
public enum CommonProfessions: EntitySubtype {
case plain(PlainCommonProfessions)
case grouped(GroupedCommonProfessions)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case plain = "plain"
case grouped = "grouped"
}
private enum Discriminator: String, Decodable {
case plain = "Plain"
case grouped = "Grouped"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .plain:
self = .plain(try container.decode(PlainCommonProfessions.self, forKey: .plain))
case .grouped:
self = .grouped(try container.decode(GroupedCommonProfessions.self, forKey: .grouped))
}
}
}
public typealias CommonnessRatedSkill = SkillReference
public struct CulturalPackageItem: EntitySubtype {
/// The skill's identifier.
public let id: SkillIdentifier
/// The skill points for the respective skill you get for buying the cultural package.
public let points: Int
public init(id: SkillIdentifier, points: Int) {
self.id = id
self.points = points
}
}
public struct CultureTranslation: EntitySubtype {
/// The name of the state.
public let name: String
/// The description of the area knowledge.
public let areaKnowledge: AreaKnowledgeTranslation
/// The respective common advantages text from the source book.
public let commonAdvantages: NonEmptyString?
/// The respective common disadvantages text from the source book.
public let commonDisadvantages: NonEmptyString?
/// The respective uncommon advantages text from the source book.
public let uncommonAdvantages: NonEmptyString?
/// The respective uncommon disadvantages text from the source book.
public let uncommonDisadvantages: NonEmptyString?
/// Structured description of common names.
public let commonNames: CommonNames
public let errata: Errata?
public init(name: String, areaKnowledge: AreaKnowledgeTranslation, commonAdvantages: NonEmptyString? = nil, commonDisadvantages: NonEmptyString? = nil, uncommonAdvantages: NonEmptyString? = nil, uncommonDisadvantages: NonEmptyString? = nil, commonNames: CommonNames, errata: Errata? = nil) {
self.name = name
self.areaKnowledge = areaKnowledge
self.commonAdvantages = commonAdvantages
self.commonDisadvantages = commonDisadvantages
self.uncommonAdvantages = uncommonAdvantages
self.uncommonDisadvantages = uncommonDisadvantages
self.commonNames = commonNames
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case areaKnowledge = "area_knowledge"
case commonAdvantages = "common_advantages"
case commonDisadvantages = "common_disadvantages"
case uncommonAdvantages = "uncommon_advantages"
case uncommonDisadvantages = "uncommon_disadvantages"
case commonNames = "common_names"
case errata = "errata"
}
}
/// Description and examples of the area knowledge.
public struct AreaKnowledgeTranslation: EntitySubtype {
/// The full description without examples in parenthesis.
public let description: String
/// A shorter version of the description, used in input fields and other UI elements where the space might be to small to use the full description.
public let abbreviated: String
/// Examples of areas, if applicable.
public let examples: [AreaKnowledgeExample]?
public init(description: String, abbreviated: String, examples: [AreaKnowledgeExample]? = nil) {
self.description = description
self.abbreviated = abbreviated
self.examples = examples
}
}
public struct AreaKnowledgeExample: EntitySubtype {
public let area: String
public init(area: String) {
self.area = area
}
}
/// Structured description of common names.
public struct CommonNames: EntitySubtype {
/// First names can be gender-neutral, but they can also be for a specific binary sex. They are sorted into groups.
public let firstNameGroups: [CommonNameGroup]?
/// Last names can be gender-neutral, like family names, but they can also be for a specific binary sex. They are sorted into groups.
public let lastNameGroups: [CommonNameGroup]?
/// Special naming rules.
public let namingRules: NonEmptyString?
public init(firstNameGroups: [CommonNameGroup]? = nil, lastNameGroups: [CommonNameGroup]? = nil, namingRules: NonEmptyString? = nil) {
self.firstNameGroups = firstNameGroups
self.lastNameGroups = lastNameGroups
self.namingRules = namingRules
}
private enum CodingKeys: String, CodingKey {
case firstNameGroups = "first_name_groups"
case lastNameGroups = "last_name_groups"
case namingRules = "naming_rules"
}
}
public struct CommonNameGroup: EntitySubtype {
/// The group label.
public let label: NonEmptyString
/// The binary sex if the group is only for a certain binary sex.
public let sex: BinarySex?
/// The names from the group.
public let names: [CommonName]
public init(label: NonEmptyString, sex: BinarySex? = nil, names: [CommonName]) {
self.label = label
self.sex = sex
self.names = names
}
}
public struct CommonName: EntitySubtype {
public let name: NonEmptyString
/// Additional information about the name, appended in parenthesis.
public let note: NonEmptyString?
public init(name: NonEmptyString, note: NonEmptyString? = nil) {
self.name = name
self.note = note
}
}
@@ -0,0 +1,64 @@
//
// DerivedCharacteristic.swift
// OptolithDatabaseSchema
//
public struct DerivedCharacteristic: LocalizableEntity {
/// An unique, increasing integer.
public let id: Int
public let prerequisites: DerivedCharacteristicPrerequisites?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DerivedCharacteristicTranslation>
public init(id: Int, prerequisites: DerivedCharacteristicPrerequisites? = nil, src: PublicationRefs, translations: LocaleMap<DerivedCharacteristicTranslation>) {
self.id = id
self.prerequisites = prerequisites
self.src = src
self.translations = translations
}
}
public struct DerivedCharacteristicTranslation: EntitySubtype {
/// The characteristic's name.
public let name: String
/// The characteristic's abbreviation.
public let abbreviation: String
/// Possible calculation strings for the final value.
public let calculation: CalculationTranslation?
public init(name: String, abbreviation: String, calculation: CalculationTranslation? = nil) {
self.name = name
self.abbreviation = abbreviation
self.calculation = calculation
}
}
/// Possible calculation strings for the final value.
public struct CalculationTranslation: EntitySubtype {
/// The default calculation string.
public let `default`: String
/// The calculation string if only half of the primary attribute is used.
public let halfPrimary: String?
/// The calculation string if no primary attribute is used.
public let noPrimary: String?
public init(`default`: String, halfPrimary: String? = nil, noPrimary: String? = nil) {
self.`default` = `default`
self.halfPrimary = halfPrimary
self.noPrimary = noPrimary
}
private enum CodingKeys: String, CodingKey {
case `default` = "default"
case halfPrimary = "half_primary"
case noPrimary = "no_primary"
}
}
@@ -0,0 +1,94 @@
//
// Disadvantage.swift
// OptolithDatabaseSchema
//
public struct Disadvantage: LocalizableEntity {
public let id: Id
public let levels: Levels?
public let selectOptions: SelectOptions?
public let maximum: Maximum?
public let prerequisites: AdvantageDisadvantagePrerequisites?
public let apValue: AdventurePointsValue
/// Does this disadvantage count towards the maximum of AP to be spent on disadvantages?
public let hasMaximumSpentInfluence: Bool
/// Does this disadvantage exclusively applies to arcane spellworks and not to magical actions and magical applications?
public let isExclusiveToArcaneSpellworks: Bool
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DisadvantageTranslation>
public init(id: Id, levels: Levels? = nil, selectOptions: SelectOptions? = nil, maximum: Maximum? = nil, prerequisites: AdvantageDisadvantagePrerequisites? = nil, apValue: AdventurePointsValue, hasMaximumSpentInfluence: Bool, isExclusiveToArcaneSpellworks: Bool, src: PublicationRefs, translations: LocaleMap<DisadvantageTranslation>) {
self.id = id
self.levels = levels
self.selectOptions = selectOptions
self.maximum = maximum
self.prerequisites = prerequisites
self.apValue = apValue
self.hasMaximumSpentInfluence = hasMaximumSpentInfluence
self.isExclusiveToArcaneSpellworks = isExclusiveToArcaneSpellworks
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case levels = "levels"
case selectOptions = "select_options"
case maximum = "maximum"
case prerequisites = "prerequisites"
case apValue = "ap_value"
case hasMaximumSpentInfluence = "has_maximum_spent_influence"
case isExclusiveToArcaneSpellworks = "is_exclusive_to_arcane_spellworks"
case src = "src"
case translations = "translations"
}
}
public struct DisadvantageTranslation: EntitySubtype {
public let name: Name
public let nameInLibrary: NameInLibrary?
/// A string that is used as a label for an input field.
public let input: Input?
public let rules: Rules
/// The range.
public let range: String?
/// A string that gets appended to the default AP Value text with a preceding space. This always happens if present, even if the generated AP Value text is replaced.
public let apValueAppend: AdventurePointsValueAppend?
public let errata: Errata?
public init(name: Name, nameInLibrary: NameInLibrary? = nil, input: Input? = nil, rules: Rules, range: String? = nil, apValueAppend: AdventurePointsValueAppend? = nil, errata: Errata? = nil) {
self.name = name
self.nameInLibrary = nameInLibrary
self.input = input
self.rules = rules
self.range = range
self.apValueAppend = apValueAppend
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case nameInLibrary = "name_in_library"
case input = "input"
case rules = "rules"
case range = "range"
case apValueAppend = "ap_value_append"
case errata = "errata"
}
}
@@ -0,0 +1,32 @@
//
// Disease.swift
// OptolithDatabaseSchema
//
public struct Disease: LocalizableEntity {
/// The disease's identifier. An unique, increasing integer.
public let id: Int
/// The disease's level.
public let level: Int
/// Depending on the disease, apply Spirit or Toughness as a penalty to the disease roll. It may also happen that the lower of both is applied as a penalty.
public let resistance: Resistance
/// What causes the disease? The GM rolls 1D20 to see if a character gets infected. If the infection check succeeds, the GM makes a disease check to determine the severity of the infection.
public let cause: [Cause]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DiseaseTranslation>
public init(id: Int, level: Int, resistance: Resistance, cause: [Cause], src: PublicationRefs, translations: LocaleMap<DiseaseTranslation>) {
self.id = id
self.level = level
self.resistance = resistance
self.cause = cause
self.src = src
self.translations = translations
}
}
@@ -0,0 +1,26 @@
//
// Element.swift
// OptolithDatabaseSchema
//
public struct Element: LocalizableEntity {
/// The element's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ElementTranslation>
public init(id: Int, translations: LocaleMap<ElementTranslation>) {
self.id = id
self.translations = translations
}
}
public struct ElementTranslation: EntitySubtype {
/// The element's name.
public let name: String
public init(name: String) {
self.name = name
}
}
@@ -0,0 +1,67 @@
//
// ExperienceLevel.swift
// OptolithDatabaseSchema
//
/// Adventure Points and maximum values at hero creation.
public struct ExperienceLevel: LocalizableEntity {
/// An unique, increasing integer.
public let id: Int
/// The AP value you get.
public let adventurePoints: Int
/// The highest possible attribute value.
public let maxAttributeValue: Int
/// The highest possible skill rating.
public let maxSkillRating: Int
/// The highest possible combat technique rating.
public let maxCombatTechniqueRating: Int
/// The limit for the sum of all attribute values.
public let maxAttributeTotal: Int
/// The maximum of spells/chants you can activate.
public let maxNumberOfSpellsLiturgicalChants: Int
/// The maximum of spells of an unfamiliar tradition you can activate.
public let maxNumberOfUnfamiliarSpells: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ExperienceLevelTranslation>
public init(id: Int, adventurePoints: Int, maxAttributeValue: Int, maxSkillRating: Int, maxCombatTechniqueRating: Int, maxAttributeTotal: Int, maxNumberOfSpellsLiturgicalChants: Int, maxNumberOfUnfamiliarSpells: Int, translations: LocaleMap<ExperienceLevelTranslation>) {
self.id = id
self.adventurePoints = adventurePoints
self.maxAttributeValue = maxAttributeValue
self.maxSkillRating = maxSkillRating
self.maxCombatTechniqueRating = maxCombatTechniqueRating
self.maxAttributeTotal = maxAttributeTotal
self.maxNumberOfSpellsLiturgicalChants = maxNumberOfSpellsLiturgicalChants
self.maxNumberOfUnfamiliarSpells = maxNumberOfUnfamiliarSpells
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case adventurePoints = "adventure_points"
case maxAttributeValue = "max_attribute_value"
case maxSkillRating = "max_skill_rating"
case maxCombatTechniqueRating = "max_combat_technique_rating"
case maxAttributeTotal = "max_attribute_total"
case maxNumberOfSpellsLiturgicalChants = "max_number_of_spells_liturgical_chants"
case maxNumberOfUnfamiliarSpells = "max_number_of_unfamiliar_spells"
case translations = "translations"
}
}
public struct ExperienceLevelTranslation: EntitySubtype {
/// The name of the experience level.
public let name: String
public init(name: String) {
self.name = name
}
}
@@ -0,0 +1,26 @@
//
// EyeColor.swift
// OptolithDatabaseSchema
//
public struct EyeColor: LocalizableEntity {
/// The eye color's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<EyeColorTranslation>
public init(id: Int, translations: LocaleMap<EyeColorTranslation>) {
self.id = id
self.translations = translations
}
}
public struct EyeColorTranslation: EntitySubtype {
/// The eye color.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,409 @@
//
// FamiliarsTrick.swift
// OptolithDatabaseSchema
//
public struct FamiliarsTrick: LocalizableEntity {
/// The familiar's trick's identifier. An unique, increasing integer.
public let id: Int
/// The animal types this trick is available to. Either it is available to all or only a list of specific animal types.
///
/// If no animal types are given, the animal disease applies to all animal types.
public let animalTypes: [AnimalTypeReference]
/// Measurable parameters of a familiar's trick.
public let parameters: FamiliarsTrickPerformanceParameters
/// The property of the trick.
public let property: FamiliarsTrickProperty
/// The AP value the familiar has to pay for. It may also be that a specific is known by all familiar by default. In the latter case the field is not set.
public let apValue: Int?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<FamiliarsTrickTranslation>
public init(id: Int, animalTypes: [AnimalTypeReference], parameters: FamiliarsTrickPerformanceParameters, property: FamiliarsTrickProperty, apValue: Int? = nil, src: PublicationRefs, translations: LocaleMap<FamiliarsTrickTranslation>) {
self.id = id
self.animalTypes = animalTypes
self.parameters = parameters
self.property = property
self.apValue = apValue
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case animalTypes = "animal_types"
case parameters = "parameters"
case property = "property"
case apValue = "ap_value"
case src = "src"
case translations = "translations"
}
}
public enum FamiliarsTrickProperty: EntitySubtype {
case fixed(PropertyReference)
case indefinite(IndefiniteFamiliarsTrickProperty)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case fixed = "fixed"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case fixed = "Fixed"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .fixed:
self = .fixed(try container.decode(PropertyReference.self, forKey: .fixed))
case .indefinite:
self = .indefinite(try container.decode(IndefiniteFamiliarsTrickProperty.self, forKey: .indefinite))
}
}
}
public struct IndefiniteFamiliarsTrickProperty: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<IndefiniteFamiliarsTrickPropertyTranslation>
public init(translations: LocaleMap<IndefiniteFamiliarsTrickPropertyTranslation>) {
self.translations = translations
}
}
public struct IndefiniteFamiliarsTrickPropertyTranslation: EntitySubtype {
/// A description of the property.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
public struct FamiliarsTrickTranslation: EntitySubtype {
/// The name of the familiar's trick.
public let name: String
/// The effect description.
public let effect: String
@available(*, deprecated)
public let cost: OldParameter
@available(*, deprecated)
public let duration: OldParameter
public let errata: Errata?
public init(name: String, effect: String, cost: OldParameter, duration: OldParameter, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.cost = cost
self.duration = duration
self.errata = errata
}
}
/// Measurable parameters of a familiar's trick.
public enum FamiliarsTrickPerformanceParameters: EntitySubtype {
case oneTime(FamiliarsTrickOneTimePerformanceParameters)
case oneTimeInterval(FamiliarsTrickOneTimeIntervalPerformanceParameters)
case sustained(FamiliarsTrickSustainedPerformanceParameters)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case oneTime = "one_time"
case oneTimeInterval = "one_time_interval"
case sustained = "sustained"
}
private enum Discriminator: String, Decodable {
case oneTime = "OneTime"
case oneTimeInterval = "OneTimeInterval"
case sustained = "Sustained"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .oneTime:
self = .oneTime(try container.decode(FamiliarsTrickOneTimePerformanceParameters.self, forKey: .oneTime))
case .oneTimeInterval:
self = .oneTimeInterval(try container.decode(FamiliarsTrickOneTimeIntervalPerformanceParameters.self, forKey: .oneTimeInterval))
case .sustained:
self = .sustained(try container.decode(FamiliarsTrickSustainedPerformanceParameters.self, forKey: .sustained))
}
}
}
public struct FamiliarsTrickOneTimePerformanceParameters: EntitySubtype {
public let cost: FamiliarsTrickOneTimeCost
public let duration: FamiliarsTrickOneTimeDuration
public init(cost: FamiliarsTrickOneTimeCost, duration: FamiliarsTrickOneTimeDuration) {
self.cost = cost
self.duration = duration
}
}
public enum FamiliarsTrickOneTimeCost: EntitySubtype {
case fixed(FamiliarsTrickFixedOneTimeCost)
case all(FamiliarsTrickAllOneTimeCost)
case indefinite(FamiliarsTrickIndefiniteOneTimeCost)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case fixed = "fixed"
case all = "all"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case fixed = "Fixed"
case all = "All"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .fixed:
self = .fixed(try container.decode(FamiliarsTrickFixedOneTimeCost.self, forKey: .fixed))
case .all:
self = .all(try container.decode(FamiliarsTrickAllOneTimeCost.self, forKey: .all))
case .indefinite:
self = .indefinite(try container.decode(FamiliarsTrickIndefiniteOneTimeCost.self, forKey: .indefinite))
}
}
}
public struct FamiliarsTrickFixedOneTimeCost: EntitySubtype {
/// The AE cost value.
public let aeValue: Int
/// The LP cost value.
public let lpValue: Int?
/// The interval in which you have to pay the AE cost again.
public let interval: DurationUnitValue?
/// All translations for the entry, identified by IETF language tag
/// (BCP47).
public let translations: LocaleMap<FamiliarsTrickFixedOneTimeCostTranslation>?
public init(aeValue: Int, lpValue: Int? = nil, interval: DurationUnitValue? = nil, translations: LocaleMap<FamiliarsTrickFixedOneTimeCostTranslation>? = nil) {
self.aeValue = aeValue
self.lpValue = lpValue
self.interval = interval
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case aeValue = "ae_value"
case lpValue = "lp_value"
case interval = "interval"
case translations = "translations"
}
}
public struct FamiliarsTrickFixedOneTimeCostTranslation: EntitySubtype {
/// The cost have to be per a specific countable entity, e.g. `8 KP per person`.
public let per: ResponsiveTextOptional?
public init(per: ResponsiveTextOptional? = nil) {
self.per = per
}
}
public struct FamiliarsTrickAllOneTimeCost: EntitySubtype {
/// The minimum AE the familiar has to have.
public let minimum: Int?
public init(minimum: Int? = nil) {
self.minimum = minimum
}
}
public struct FamiliarsTrickIndefiniteOneTimeCost: EntitySubtype {
/// All translations for the entry, identified by IETF language tag
/// (BCP47).
public let translations: LocaleMap<FamiliarsTrickIndefiniteOneTimeCostTranslation>
public init(translations: LocaleMap<FamiliarsTrickIndefiniteOneTimeCostTranslation>) {
self.translations = translations
}
}
public struct FamiliarsTrickIndefiniteOneTimeCostTranslation: EntitySubtype {
/// A description of the AE cost.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
public enum FamiliarsTrickOneTimeDuration: EntitySubtype {
case immediate
case fixed(FamiliarsTrickFixedOneTimeDuration)
case indefinite(FamiliarsTrickIndefiniteOneTimeDuration)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case immediate = "immediate"
case fixed = "fixed"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case immediate = "Immediate"
case fixed = "Fixed"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .immediate:
self = .immediate
case .fixed:
self = .fixed(try container.decode(FamiliarsTrickFixedOneTimeDuration.self, forKey: .fixed))
case .indefinite:
self = .indefinite(try container.decode(FamiliarsTrickIndefiniteOneTimeDuration.self, forKey: .indefinite))
}
}
}
public struct FamiliarsTrickFixedOneTimeDuration: EntitySubtype {
/// If the duration is the maximum duration, so it may end earlier.
public let isMaximum: Bool?
/// The (unitless) duration.
public let value: Int
/// The duration unit.
public let unit: DurationUnit
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<FamiliarsTrickFixedOneTimeDurationTranslation>?
public init(isMaximum: Bool? = nil, value: Int, unit: DurationUnit, translations: LocaleMap<FamiliarsTrickFixedOneTimeDurationTranslation>? = nil) {
self.isMaximum = isMaximum
self.value = value
self.unit = unit
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case value = "value"
case unit = "unit"
case translations = "translations"
}
}
public struct FamiliarsTrickFixedOneTimeDurationTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace?
public init(replacement: ResponsiveTextReplace? = nil) {
self.replacement = replacement
}
}
public struct FamiliarsTrickIndefiniteOneTimeDuration: EntitySubtype {
/// All translations for the entry, identified by IETF language tag
/// (BCP47).
public let translations: LocaleMap<FamiliarsTrickIndefiniteOneTimeDurationTranslation>
public init(translations: LocaleMap<FamiliarsTrickIndefiniteOneTimeDurationTranslation>) {
self.translations = translations
}
}
public struct FamiliarsTrickIndefiniteOneTimeDurationTranslation: EntitySubtype {
/// A description of the duration.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
public struct FamiliarsTrickOneTimeIntervalPerformanceParameters: EntitySubtype {
public let cost: FamiliarsTrickOneTimeIntervalCost
public init(cost: FamiliarsTrickOneTimeIntervalCost) {
self.cost = cost
}
}
public struct FamiliarsTrickOneTimeIntervalCost: EntitySubtype {
/// The AE cost value.
public let aeValue: Int
/// The LP cost value.
public let lpValue: Int?
/// The duration granted/added by paying the given AE cost.
public let interval: DurationUnitValue
public init(aeValue: Int, lpValue: Int? = nil, interval: DurationUnitValue) {
self.aeValue = aeValue
self.lpValue = lpValue
self.interval = interval
}
private enum CodingKeys: String, CodingKey {
case aeValue = "ae_value"
case lpValue = "lp_value"
case interval = "interval"
}
}
public struct FamiliarsTrickSustainedPerformanceParameters: EntitySubtype {
public let cost: FamiliarsTrickSustainedCost
public init(cost: FamiliarsTrickSustainedCost) {
self.cost = cost
}
}
public struct FamiliarsTrickSustainedCost: EntitySubtype {
/// The AE cost value.
public let aeValue: Int
/// The LP cost value.
public let lpValue: Int?
/// The interval in which you have to pay the AE cost again, if any.
public let interval: DurationUnitValue?
public init(aeValue: Int, lpValue: Int? = nil, interval: DurationUnitValue? = nil) {
self.aeValue = aeValue
self.lpValue = lpValue
self.interval = interval
}
private enum CodingKeys: String, CodingKey {
case aeValue = "ae_value"
case lpValue = "lp_value"
case interval = "interval"
}
}
@@ -0,0 +1,26 @@
//
// HairColor.swift
// OptolithDatabaseSchema
//
public struct HairColor: LocalizableEntity {
/// The hair color's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<HairColorTranslation>
public init(id: Int, translations: LocaleMap<HairColorTranslation>) {
self.id = id
self.translations = translations
}
}
public struct HairColorTranslation: EntitySubtype {
/// The hair color.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,6 @@
//
// Kirchenpraegung.swift
// OptolithDatabaseSchema
//
public typealias Kirchenpraegung = Influence
@@ -0,0 +1,324 @@
//
// Lessons_Curriculum.swift
// OptolithDatabaseSchema
//
/// This is a curriculum of a specified academy, containing the guideline, elective and restricted spellworks as well as the lesson packages of that academy.
public struct Curriculum: LocalizableEntity {
/// The curriculum's identifier. An unique, increasing integer.
public let id: Int
/// The institution's guideline.
public let guideline: GuidelineReference
/// The academy's elective spellworks package.
public let electiveSpellworks: ElectiveSpellworks?
/// The academy's restricted spellworks package.
public let restrictedSpellworks: RestrictedSpellworks?
/// A list of available lesson packages.
public let lessonPackages: LessonPackages
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CurriculumTranslation>
public init(id: Int, guideline: GuidelineReference, electiveSpellworks: ElectiveSpellworks? = nil, restrictedSpellworks: RestrictedSpellworks? = nil, lessonPackages: LessonPackages, src: PublicationRefs, translations: LocaleMap<CurriculumTranslation>) {
self.id = id
self.guideline = guideline
self.electiveSpellworks = electiveSpellworks
self.restrictedSpellworks = restrictedSpellworks
self.lessonPackages = lessonPackages
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case guideline = "guideline"
case electiveSpellworks = "elective_spellworks"
case restrictedSpellworks = "restricted_spellworks"
case lessonPackages = "lesson_packages"
case src = "src"
case translations = "translations"
}
}
public struct CurriculumTranslation: EntitySubtype {
/// The name of the academy.
public let name: NonEmptyString
public let errata: Errata?
public init(name: NonEmptyString, errata: Errata? = nil) {
self.name = name
self.errata = errata
}
}
/// The academy's elective spellworks package.
public enum ElectiveSpellworks: EntitySubtype {
case definedByGameMaster
case specific(SpecificElectiveSpellworks)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case definedByGameMaster = "defined_by_game_master"
case specific = "specific"
}
private enum Discriminator: String, Decodable {
case definedByGameMaster = "DefinedByGameMaster"
case specific = "Specific"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .definedByGameMaster:
self = .definedByGameMaster
case .specific:
self = .specific(try container.decode(SpecificElectiveSpellworks.self, forKey: .specific))
}
}
}
public struct SpecificElectiveSpellworks: EntitySubtype {
public let list: [ElectiveSpellwork]
public init(list: [ElectiveSpellwork]) {
self.list = list
}
}
public struct ElectiveSpellwork: EntitySubtype {
public let id: SpellworkIdentifier
/// The elective spellwork may only take effect if a certain condition is met. The condition may be related to professions or profession variants, but it is designed so that it can work without a specific profession, as multiple may belong to an institute, but with referencing other entities instead.
public let restriction: ElectiveSpellworkRestriction?
public init(id: SpellworkIdentifier, restriction: ElectiveSpellworkRestriction? = nil) {
self.id = id
self.restriction = restriction
}
}
/// The elective spellwork may only take effect if a certain condition is met. The condition may be related to professions or profession variants, but it is designed so that it can work without a specific profession, as multiple may belong to an institute, but with referencing other entities instead.
public struct ElectiveSpellworkRestriction: EntitySubtype {
public let tag: String
public let element: ElectiveSpellworkElementRestriction
public init(tag: String, element: ElectiveSpellworkElementRestriction) {
self.tag = tag
self.element = element
}
}
public struct ElectiveSpellworkElementRestriction: EntitySubtype {
public let id: ElementIdentifier
public init(id: ElementIdentifier) {
self.id = id
}
}
/// The academy's restricted spellworks package.
public typealias RestrictedSpellworks = [RestrictedSpellwork]
/// The academy's restricted spellworks package.
public enum RestrictedSpellwork: EntitySubtype {
case property(RestrictedProperty)
case spellwork(SpellworkIdentifier)
case demonSummoning
case borbaradian
case damageIntelligent
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case property = "property"
case spellwork = "spellwork"
case demonSummoning = "demon_summoning"
case borbaradian = "borbaradian"
case damageIntelligent = "damage_intelligent"
}
private enum Discriminator: String, Decodable {
case property = "Property"
case spellwork = "Spellwork"
case demonSummoning = "DemonSummoning"
case borbaradian = "Borbaradian"
case damageIntelligent = "DamageIntelligent"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .property:
self = .property(try container.decode(RestrictedProperty.self, forKey: .property))
case .spellwork:
self = .spellwork(try container.decode(SpellworkIdentifier.self, forKey: .spellwork))
case .demonSummoning:
self = .demonSummoning
case .borbaradian:
self = .borbaradian
case .damageIntelligent:
self = .damageIntelligent
}
}
}
public struct RestrictedProperty: EntitySubtype {
/// The identifier of the property that spellworks are disallowed from.
public let id: PropertyIdentifier
/// Exclude specific spellworks from the restriction.
public let exclude: [SpellworkIdentifier]?
/// Spellworks from this property up to a certain number are allowed. Spellworks excluded from this restriction definition using `exclude` do not contribute to the maximum.
public let maximum: Double?
public init(id: PropertyIdentifier, exclude: [SpellworkIdentifier]? = nil, maximum: Double? = nil) {
self.id = id
self.exclude = exclude
self.maximum = maximum
}
}
/// A list of available lesson packages.
public typealias LessonPackages = [LessonPackage]
public struct LessonPackage: EntitySubtype {
/// The lesson package's identifier. An unique, increasing integer.
public let id: Int
/// The spell values difference of the lesson package. This field reflects the changes (difference) to the field of the same name in the profession package. If a spell gets to SR 0 because of this, it will be removed completely.
public let spellworkChanges: [SpellworkChange]?
public let skills: [AbilityAdjustment]?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<LessonPackageTranslation>
public init(id: Int, spellworkChanges: [SpellworkChange]? = nil, skills: [AbilityAdjustment]? = nil, translations: LocaleMap<LessonPackageTranslation>) {
self.id = id
self.spellworkChanges = spellworkChanges
self.skills = skills
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case spellworkChanges = "spellwork_changes"
case skills = "skills"
case translations = "translations"
}
}
public struct SpellworkChange: EntitySubtype {
public let base: SpellworkAdjustment
public let replacement: SpellworkAdjustment
public init(base: SpellworkAdjustment, replacement: SpellworkAdjustment) {
self.base = base
self.replacement = replacement
}
}
public struct LessonPackageTranslation: EntitySubtype {
/// The name of the lesson package.
public let name: NonEmptyString
/// 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.
public let spellworkChanges: NonEmptyString?
public init(name: NonEmptyString, spellworkChanges: NonEmptyString? = nil) {
self.name = name
self.spellworkChanges = spellworkChanges
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case spellworkChanges = "spellwork_changes"
}
}
public enum AbilityAdjustment: EntitySubtype {
case combatTechnique(CombatTechniqueAdjustment)
case skill(SkillAdjustment)
case spellwork(SpellworkAdjustment)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case combatTechnique = "combat_technique"
case skill = "skill"
case spellwork = "spellwork"
}
private enum Discriminator: String, Decodable {
case combatTechnique = "CombatTechnique"
case skill = "Skill"
case spellwork = "Spellwork"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .combatTechnique:
self = .combatTechnique(try container.decode(CombatTechniqueAdjustment.self, forKey: .combatTechnique))
case .skill:
self = .skill(try container.decode(SkillAdjustment.self, forKey: .skill))
case .spellwork:
self = .spellwork(try container.decode(SpellworkAdjustment.self, forKey: .spellwork))
}
}
}
public struct CombatTechniqueAdjustment: EntitySubtype {
public let id: CombatTechniqueIdentifier
/// The combat technique points that will be added to the current combat technique rating.
public let points: Int
public init(id: CombatTechniqueIdentifier, points: Int) {
self.id = id
self.points = points
}
}
public struct SkillAdjustment: EntitySubtype {
/// The skill's identifier.
public let id: SkillIdentifier
/// The skill points that will be added to the current skill rating.
public let points: Int
public init(id: SkillIdentifier, points: Int) {
self.id = id
self.points = points
}
}
public struct SpellworkAdjustment: EntitySubtype {
public let id: SpellworkIdentifier
/// The skill points that will be added to the current skill rating. If a spell gets to a skill rating of 0 because of this, it will be removed completely.
public let points: Int
/// The target tradition. If the target spell is not from the Guild Mage tradition, specify the tradition identifier here.
public let tradition: MagicalTraditionIdentifier?
public init(id: SpellworkIdentifier, points: Int, tradition: MagicalTraditionIdentifier? = nil) {
self.id = id
self.points = points
self.tradition = tradition
}
}
@@ -0,0 +1,36 @@
//
// Lessons_Guideline.swift
// OptolithDatabaseSchema
//
public struct Guideline: LocalizableEntity {
/// The guideline's identifier. An unique, increasing integer.
public let id: Int
/// Maximum number of spells that can be exchanged.
public let spellworkChangesAllowed: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<GuidelineTranslation>
public init(id: Int, spellworkChangesAllowed: Int, translations: LocaleMap<GuidelineTranslation>) {
self.id = id
self.spellworkChangesAllowed = spellworkChangesAllowed
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case spellworkChangesAllowed = "spellwork_changes_allowed"
case translations = "translations"
}
}
public struct GuidelineTranslation: EntitySubtype {
/// The guideline name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,116 @@
//
// LiturgicalChant.swift
// OptolithDatabaseSchema
//
public struct LiturgicalChant: LocalizableEntity {
/// The liturgical chant's identifier. An unique, increasing integer.
public let id: Int
/// Lists the linked three attributes used to make a skill check.
public let check: SkillCheck
/// In some cases, the target's Spirit or Toughness is applied as a penalty.
public let checkPenalty: SkillCheckPenalty?
/// Measurable parameters of a liturgical chant.
public let parameters: FastPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
/// The tradition(s) the liturgical chant is available for. Note that general aspects do not have an associated tradition and thus need to be defined in a special way.
public let traditions: [SkillTradition]
/// States which column is used to improve the skill.
public let improvementCost: ImprovementCost
public let prerequisites: LiturgyPrerequisites?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<LiturgicalChantTranslation>
public let enhancements: Enhancements?
public init(id: Int, check: SkillCheck, checkPenalty: SkillCheckPenalty? = nil, parameters: FastPerformanceParameters, `target`: AffectedTargetCategories, traditions: [SkillTradition], improvementCost: ImprovementCost, prerequisites: LiturgyPrerequisites? = nil, src: PublicationRefs, translations: LocaleMap<LiturgicalChantTranslation>, enhancements: Enhancements? = nil) {
self.id = id
self.check = check
self.checkPenalty = checkPenalty
self.parameters = parameters
self.`target` = `target`
self.traditions = traditions
self.improvementCost = improvementCost
self.prerequisites = prerequisites
self.src = src
self.translations = translations
self.enhancements = enhancements
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case check = "check"
case checkPenalty = "check_penalty"
case parameters = "parameters"
case `target` = "target"
case traditions = "traditions"
case improvementCost = "improvement_cost"
case prerequisites = "prerequisites"
case src = "src"
case translations = "translations"
case enhancements = "enhancements"
}
}
public struct LiturgicalChantTranslation: EntitySubtype {
/// The name of the liturgical chant.
public let name: NonEmptyString
/// A compressed name of the liturgical chant for use in small areas (e.g. on character sheet). Should only be defined if the `name` does not fit on character sheet.
public let nameCompressed: NonEmptyString?
/// The effect description may be either a plain text or a text that is divided by a list of effects for each quality level. It may also be a list for each two quality levels.
public let effect: ActivatableSkillEffect
@available(*, deprecated)
public let castingTime: OldParameter
@available(*, deprecated)
public let cost: OldParameter
@available(*, deprecated)
public let range: OldParameter
@available(*, deprecated)
public let duration: OldParameter
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: NonEmptyString, nameCompressed: NonEmptyString? = nil, effect: ActivatableSkillEffect, castingTime: OldParameter, cost: OldParameter, range: OldParameter, duration: OldParameter, `target`: String, errata: Errata? = nil) {
self.name = name
self.nameCompressed = nameCompressed
self.effect = effect
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case nameCompressed = "name_compressed"
case effect = "effect"
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
@@ -0,0 +1,32 @@
//
// Locale.swift
// OptolithDatabaseSchema
//
public struct Locale: Entity {
/// The locale's identifier. An IETF language tag (BCP47).
public let id: String
/// Name of the language in it's language.
public let name: String
/// Region in which the language is spoken in it's language.
public let region: String
/// The language is not (fully) implemented and thus needs to be excluded from stable releases.
public let isMissingImplementation: Bool?
public init(id: String, name: String, region: String, isMissingImplementation: Bool? = nil) {
self.id = id
self.name = name
self.region = region
self.isMissingImplementation = isMissingImplementation
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case region = "region"
case isMissingImplementation = "is_missing_implementation"
}
}
@@ -0,0 +1,41 @@
//
// MetaCondition.swift
// OptolithDatabaseSchema
//
/// Meta Conditions are rule elements that work like conditions in the sense that they have four levels with different effects, but which are not explicitly listed as conditions.
public struct MetaCondition: LocalizableEntity {
/// The meta condition's identifier. An unique, increasing integer.
public let id: Int
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<MetaConditionTranslation>
public init(id: Int, src: PublicationRefs, translations: LocaleMap<MetaConditionTranslation>) {
self.id = id
self.src = src
self.translations = translations
}
}
public struct MetaConditionTranslation: EntitySubtype {
/// The meta condition's name.
public let name: NonEmptyString
/// Additional rules for the meta condition, if applicable.
public let rules: NonEmptyMarkdown?
/// The effects for level 1 to 4.
public let effects: [NonEmptyMarkdown]
public let errata: Errata?
public init(name: NonEmptyString, rules: NonEmptyMarkdown? = nil, effects: [NonEmptyMarkdown], errata: Errata? = nil) {
self.name = name
self.rules = rules
self.effects = effects
self.errata = errata
}
}
@@ -0,0 +1,84 @@
//
// PactCategory.swift
// OptolithDatabaseSchema
//
public struct PactCategory: LocalizableEntity {
/// The pact category's identifier. An unique, increasing integer.
public let id: Int
/// Types of creatures in this category.
public let types: [PactType]
/// Domains in this category.
public let domains: [PactDomain]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PactCategoryTranslation>
public init(id: Int, types: [PactType], domains: [PactDomain], src: PublicationRefs, translations: LocaleMap<PactCategoryTranslation>) {
self.id = id
self.types = types
self.domains = domains
self.src = src
self.translations = translations
}
}
public struct PactCategoryTranslation: EntitySubtype {
/// The name of the pact category.
public let name: NonEmptyString
public let errata: Errata?
public init(name: NonEmptyString, errata: Errata? = nil) {
self.name = name
self.errata = errata
}
}
public struct PactType: EntitySubtype {
/// The type's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PactTypeTranslation>
public init(id: Int, translations: LocaleMap<PactTypeTranslation>) {
self.id = id
self.translations = translations
}
}
public struct PactTypeTranslation: EntitySubtype {
/// The name of the type.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
public struct PactDomain: EntitySubtype {
/// The domain's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PactDomainTranslation>
public init(id: Int, translations: LocaleMap<PactDomainTranslation>) {
self.id = id
self.translations = translations
}
}
public struct PactDomainTranslation: EntitySubtype {
/// The name of the domain.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,245 @@
//
// Patron.swift
// OptolithDatabaseSchema
//
public struct Patron: LocalizableEntity {
/// The patron's identifier. An unique, increasing integer.
public let id: Int
/// The patron's category.
public let category: PatronCategoryReference
/// The patron-specific skills.
public let skills: [SkillReference]
/// The patron is only available to a certain set of cultures. It may be available to all, it may be available to only specific ones (intersection) and it may be available to all except specific ones to the listed cultures
/// (difference).
public let culture: PatronCulture
/// The list of cultures where patrons from this category can be the primary patron of.
public let primaryPatronCultures: [CultureReference]?
/// The patron-specific powers. Used by animist power Animal Powers IIII and should only be present on animal patrons.
public let powers: AnimalPowers?
/// The patron-specific AE cost. Used by several animist forces for animal patrons.
public let aeCost: Int?
/// The patron-specific improvement cost. Used by several animist forces for animal patrons.
public let improvementCost: ImprovementCost?
/// The patron may grant common advantages that are taken into account during character creation.
///
/// *Source:* Geisterwald & Knochenklippen, p. 6-7
public let commonAdvantages: [AdvantageReference]?
/// The patron may grant common disadvantages that are taken into account during character creation.
///
/// *Source:* Geisterwald & Knochenklippen, p. 6-7
public let commonDisadvantages: [DisadvantageReference]?
/// The animist may learn spellworks common for this patron.
///
/// *Source:* Geisterwald & Knochenklippen, p. 6-7
public let commonSpellworks: [SpellworkReference]?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PatronTranslation>
public init(id: Int, category: PatronCategoryReference, skills: [SkillReference], culture: PatronCulture, primaryPatronCultures: [CultureReference]? = nil, powers: AnimalPowers? = nil, aeCost: Int? = nil, improvementCost: ImprovementCost? = nil, commonAdvantages: [AdvantageReference]? = nil, commonDisadvantages: [DisadvantageReference]? = nil, commonSpellworks: [SpellworkReference]? = nil, translations: LocaleMap<PatronTranslation>) {
self.id = id
self.category = category
self.skills = skills
self.culture = culture
self.primaryPatronCultures = primaryPatronCultures
self.powers = powers
self.aeCost = aeCost
self.improvementCost = improvementCost
self.commonAdvantages = commonAdvantages
self.commonDisadvantages = commonDisadvantages
self.commonSpellworks = commonSpellworks
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case category = "category"
case skills = "skills"
case culture = "culture"
case primaryPatronCultures = "primary_patron_cultures"
case powers = "powers"
case aeCost = "ae_cost"
case improvementCost = "improvement_cost"
case commonAdvantages = "common_advantages"
case commonDisadvantages = "common_disadvantages"
case commonSpellworks = "common_spellworks"
case translations = "translations"
}
}
public struct PatronTranslation: EntitySubtype {
/// The name of the patron.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
/// The patron cultures the patron is or is not part of. If the patron is part of all patron cultures, the set should be empty and the operation should be difference.
public struct PatronCulture: EntitySubtype {
public let `set`: [CultureReference]
public let operation: PatronCultureOperation
public init(`set`: [CultureReference], operation: PatronCultureOperation) {
self.`set` = `set`
self.operation = operation
}
private enum CodingKeys: String, CodingKey {
case `set` = "set"
case operation = "operation"
}
}
/// The set operation to combine the set of all patron cultures with the specified set of patron cultures: If they should intersect, the patron is only part of the given cultures. If they should differ, the patron is only part of the cultures that are not given.
public enum PatronCultureOperation: String, EntitySubtype {
case intersection = "Intersection"
case difference = "Difference"
}
public struct AnimalPowers: EntitySubtype {
public let level1: AnimalPowersLevel1
public let level2: AnimalPowersLevel2
public let level3: AnimalPowersLevel3
public init(level1: AnimalPowersLevel1, level2: AnimalPowersLevel2, level3: AnimalPowersLevel3) {
self.level1 = level1
self.level2 = level2
self.level3 = level3
}
}
public struct AdvantageAnimalPower: EntitySubtype {
/// The advantage's identifier.
public let id: AdvantageIdentifier
/// It grants a higher level of the advantage.
public let level: Int?
/// It grants a specific general option of the advantage.
public let option: Int?
public init(id: AdvantageIdentifier, level: Int? = nil, option: Int? = nil) {
self.id = id
self.level = level
self.option = option
}
}
public struct SkillAnimalPower: EntitySubtype {
/// The skill's identifier.
public let id: SkillIdentifier
/// The points that gets added to the skill's rating.
public let points: Int
public init(id: SkillIdentifier, points: Int) {
self.id = id
self.points = points
}
}
public enum AnimalPowerLevel1: EntitySubtype {
case advantage(AdvantageAnimalPower)
case skill(SkillAnimalPower)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case advantage = "advantage"
case skill = "skill"
}
private enum Discriminator: String, Decodable {
case advantage = "Advantage"
case skill = "Skill"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .advantage:
self = .advantage(try container.decode(AdvantageAnimalPower.self, forKey: .advantage))
case .skill:
self = .skill(try container.decode(SkillAnimalPower.self, forKey: .skill))
}
}
}
public typealias AnimalPowersLevel1 = [AnimalPowerLevel1]
public struct CombatAnimalPower: EntitySubtype {
/// The combat value.
public let id: CombatAnimalPowerType
/// The value that gets added to the combat value.
public let value: Int
public init(id: CombatAnimalPowerType, value: Int) {
self.id = id
self.value = value
}
}
public enum CombatAnimalPowerType: String, EntitySubtype {
case attack = "Attack"
case parry = "Parry"
case rangedCombat = "RangedCombat"
case dodge = "Dodge"
case damagePoints = "DamagePoints"
case protection = "Protection"
}
public struct AnimalPowerLevel2: EntitySubtype {
public let tag: String
public let combat: CombatAnimalPower
public init(tag: String, combat: CombatAnimalPower) {
self.tag = tag
self.combat = combat
}
}
public typealias AnimalPowersLevel2 = [AnimalPowerLevel2]
public struct AttributeAnimalPower: EntitySubtype {
/// The attribute's identifier.
public let id: AttributeIdentifier
/// The value that gets added to the attribute.
public let value: Int
public init(id: AttributeIdentifier, value: Int) {
self.id = id
self.value = value
}
}
public struct AnimalPowerLevel3: EntitySubtype {
public let tag: String
public let attribute: AttributeAnimalPower
public init(tag: String, attribute: AttributeAnimalPower) {
self.tag = tag
self.attribute = attribute
}
}
public typealias AnimalPowersLevel3 = [AnimalPowerLevel3]
@@ -0,0 +1,36 @@
//
// PatronCategory.swift
// OptolithDatabaseSchema
//
public struct PatronCategory: LocalizableEntity {
/// The patron category's identifier. An unique, increasing integer.
public let id: Int
/// The list of cultures where patrons from this category can be the primary patron of.
public let primaryPatronCultures: [CultureReference]
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PatronCategoryTranslation>
public init(id: Int, primaryPatronCultures: [CultureReference], translations: LocaleMap<PatronCategoryTranslation>) {
self.id = id
self.primaryPatronCultures = primaryPatronCultures
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case primaryPatronCultures = "primary_patron_cultures"
case translations = "translations"
}
}
public struct PatronCategoryTranslation: EntitySubtype {
/// The name of the patron category.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,70 @@
//
// PersonalityTrait.swift
// OptolithDatabaseSchema
//
/// A personality trait describes character aspects of a person from a certain region. Higher trait levels only cover a part of the region covered by lower-level traits.
public struct PersonalityTrait: LocalizableEntity {
/// The personality trait's identifier. An unique, increasing integer.
public let id: Int
/// The personality trait's level.
public let level: Int
public let prerequisites: PersonalityTraitPrerequisites?
/// The lower-level personality trait(s) this trait can be combined with.
public let combinationOptions: [PersonalityTraitReference]?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PersonalityTraitTranslation>
public init(id: Int, level: Int, prerequisites: PersonalityTraitPrerequisites? = nil, combinationOptions: [PersonalityTraitReference]? = nil, src: PublicationRefs, translations: LocaleMap<PersonalityTraitTranslation>) {
self.id = id
self.level = level
self.prerequisites = prerequisites
self.combinationOptions = combinationOptions
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case level = "level"
case prerequisites = "prerequisites"
case combinationOptions = "combination_options"
case src = "src"
case translations = "translations"
}
}
public struct PersonalityTraitTranslation: EntitySubtype {
/// The name of the personality trait.
public let name: NonEmptyString
/// The effects of the personality trait. They should be sorted like they are in the book.
public let effects: [PersonalityTraitEffect]
public let errata: Errata?
public init(name: NonEmptyString, effects: [PersonalityTraitEffect], errata: Errata? = nil) {
self.name = name
self.effects = effects
self.errata = errata
}
}
public struct PersonalityTraitEffect: EntitySubtype {
/// A label that is displayed and placed before the actual text.
public let label: NonEmptyString?
/// The effect text.
public let text: NonEmptyString
public init(label: NonEmptyString? = nil, text: NonEmptyString) {
self.label = label
self.text = text
}
}
@@ -0,0 +1,902 @@
//
// Profession.swift
// OptolithDatabaseSchema
//
public struct Profession: Entity {
/// The profession's identifier. An unique, increasing integer.
public let id: Int
/// The profession group.
public let group: ProfessionGroup
/// A list of professions representing the same profession but with (slightly) different stats. For example, there may be a profession in a regional sourcebook or in the core rules and a profession in an extension rulebook like Magic of Aventuria, where the profession is basically called the same and almost has the same values, but the version from Magic of Aventuria features a spell style special ability that does not exist in the core rules or regional sourcebook.
///
/// The profession representation may feature different values for different explicitly mentioned experience levels. In most cases, there is only one stats package, which targets the experience level *Experienced*.
public let versions: [ProfessionVersion]
public init(id: Int, group: ProfessionGroup, versions: [ProfessionVersion]) {
self.id = id
self.group = group
self.versions = versions
}
}
public enum ProfessionGroup: EntitySubtype {
case mundane(MundaneProfessionGroup)
case magical(MagicalProfessionGroup)
case blessed
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case mundane = "mundane"
case magical = "magical"
case blessed = "blessed"
}
private enum Discriminator: String, Decodable {
case mundane = "Mundane"
case magical = "Magical"
case blessed = "Blessed"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .mundane:
self = .mundane(try container.decode(MundaneProfessionGroup.self, forKey: .mundane))
case .magical:
self = .magical(try container.decode(MagicalProfessionGroup.self, forKey: .magical))
case .blessed:
self = .blessed
}
}
}
public enum MundaneProfessionGroup: String, EntitySubtype {
case profane = "Profane"
case fighter = "Fighter"
case religious = "Religious"
}
public struct MagicalProfessionGroup: EntitySubtype {
/// The curriculum/academy associated with this magical profession, if any.
public let curriculum: CurriculumReference?
public init(curriculum: CurriculumReference? = nil) {
self.curriculum = curriculum
}
}
public enum ProfessionVersion: EntitySubtype {
case experienced(ExperiencedProfessionPackage)
case byExperienceLevel(ProfessionPackagesForDifferentExperienceLevels)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case experienced = "experienced"
case byExperienceLevel = "by_experience_level"
}
private enum Discriminator: String, Decodable {
case experienced = "Experienced"
case byExperienceLevel = "ByExperienceLevel"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .experienced:
self = .experienced(try container.decode(ExperiencedProfessionPackage.self, forKey: .experienced))
case .byExperienceLevel:
self = .byExperienceLevel(try container.decode(ProfessionPackagesForDifferentExperienceLevels.self, forKey: .byExperienceLevel))
}
}
}
public struct ExperiencedProfessionPackage: EntitySubtype {
/// The profession representation variant's identifier. An unique, increasing integer.
public let id: Int
public let `package`: ProfessionPackage
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ProfessionTranslation>
public init(id: Int, `package`: ProfessionPackage, src: PublicationRefs, translations: LocaleMap<ProfessionTranslation>) {
self.id = id
self.`package` = `package`
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case `package` = "package"
case src = "src"
case translations = "translations"
}
}
public struct ProfessionPackagesForDifferentExperienceLevels: EntitySubtype {
/// The profession representation variant's identifier. An unique, increasing integer.
public let id: Int
public let packagesMap: [ExperienceLevelDynamicProfessionPackage]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ProfessionTranslation>
public init(id: Int, packagesMap: [ExperienceLevelDynamicProfessionPackage], src: PublicationRefs, translations: LocaleMap<ProfessionTranslation>) {
self.id = id
self.packagesMap = packagesMap
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case packagesMap = "packages_map"
case src = "src"
case translations = "translations"
}
}
public struct ExperienceLevelDynamicProfessionPackage: EntitySubtype {
/// The experience level this profession targets. The experience level must be unique for this representation.
public let experienceLevelId: Int
public let `package`: ProfessionPackage
public init(experienceLevelId: Int, `package`: ProfessionPackage) {
self.experienceLevelId = experienceLevelId
self.`package` = `package`
}
private enum CodingKeys: String, CodingKey {
case experienceLevelId = "experience_level_id"
case `package` = "package"
}
}
public struct ProfessionPackage: EntitySubtype {
/// What does the professional package cost in adventure points?
public let apValue: Int
/// 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.
public let prerequisites: ProfessionPrerequisites?
/// In some areas, the profession package grants a loose set of stats where the player must choose between different options for the profession package.
public let options: ProfessionPackageOptions?
/// Any special abilities the profession receives from the package.
public let specialAbilities: [ProfessionSpecialAbility]?
/// Provides ratings for the combat techniques that the hero receives from the package.
public let combatTechniques: [CombatTechniqueRating]?
/// The skill ratings the package grants to the hero.
public let skills: [SkillRating]?
/// The skill ratings a magical profession receives for spells; these spells are considered activated. Spells from an unfamiliar Tradition, if any, are identified as such.
public let spells: [SpellRating]?
/// Clerical professions receive these liturgical chants at the listed skill ratings. These liturgical chants are considered activated.
public let liturgicalChants: [LiturgicalChantRating]?
/// Typical advantages for the profession.
public let suggestedAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// Typical disadvantages for the profession.
public let suggestedDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// These advantages do not fit well with this profession; to be checked with the GM before taking any of them.
public let unsuitableAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// These disadvantages do not fit well with this profession; to be checked with the GM before taking any of them.
public let unsuitableDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// Provides examples of variants for the profession, which may include changes to AP values and additional or modified skill ratings, special abilities, or combat techniques, as compared to the basic profession. Usually picking a variant is optional, but there are some rare exceptions where picking a variant is required.
public let variants: ProfessionVariants?
public init(apValue: Int, prerequisites: ProfessionPrerequisites? = nil, options: ProfessionPackageOptions? = nil, specialAbilities: [ProfessionSpecialAbility]? = nil, combatTechniques: [CombatTechniqueRating]? = nil, skills: [SkillRating]? = nil, spells: [SpellRating]? = nil, liturgicalChants: [LiturgicalChantRating]? = nil, suggestedAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, suggestedDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, unsuitableAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, unsuitableDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, variants: ProfessionVariants? = nil) {
self.apValue = apValue
self.prerequisites = prerequisites
self.options = options
self.specialAbilities = specialAbilities
self.combatTechniques = combatTechniques
self.skills = skills
self.spells = spells
self.liturgicalChants = liturgicalChants
self.suggestedAdvantages = suggestedAdvantages
self.suggestedDisadvantages = suggestedDisadvantages
self.unsuitableAdvantages = unsuitableAdvantages
self.unsuitableDisadvantages = unsuitableDisadvantages
self.variants = variants
}
private enum CodingKeys: String, CodingKey {
case apValue = "ap_value"
case prerequisites = "prerequisites"
case options = "options"
case specialAbilities = "special_abilities"
case combatTechniques = "combat_techniques"
case skills = "skills"
case spells = "spells"
case liturgicalChants = "liturgical_chants"
case suggestedAdvantages = "suggested_advantages"
case suggestedDisadvantages = "suggested_disadvantages"
case unsuitableAdvantages = "unsuitable_advantages"
case unsuitableDisadvantages = "unsuitable_disadvantages"
case variants = "variants"
}
}
public struct ProfessionTranslation: EntitySubtype {
/// Name of the basic profession.
public let name: ProfessionName
/// A name addition of the profession. This will contain texts like name of the academy or the witch circle. It is enclosed in parenthesis, but the database entry must not contain parenthesis.
public let specification: ProfessionName?
/// Typical advantages for the profession.
public let suggestedAdvantages: NonEmptyString?
/// Typical disadvantages for the profession.
public let suggestedDisadvantages: NonEmptyString?
/// These advantages do not fit well with this profession; to be checked with the GM before taking any of them.
public let unsuitableAdvantages: NonEmptyString?
/// These disadvantages do not fit well with this profession; to be checked with the GM before taking any of them.
public let unsuitableDisadvantages: NonEmptyString?
public let errata: Errata?
public init(name: ProfessionName, specification: ProfessionName? = nil, suggestedAdvantages: NonEmptyString? = nil, suggestedDisadvantages: NonEmptyString? = nil, unsuitableAdvantages: NonEmptyString? = nil, unsuitableDisadvantages: NonEmptyString? = nil, errata: Errata? = nil) {
self.name = name
self.specification = specification
self.suggestedAdvantages = suggestedAdvantages
self.suggestedDisadvantages = suggestedDisadvantages
self.unsuitableAdvantages = unsuitableAdvantages
self.unsuitableDisadvantages = unsuitableDisadvantages
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case specification = "specification"
case suggestedAdvantages = "suggested_advantages"
case suggestedDisadvantages = "suggested_disadvantages"
case unsuitableAdvantages = "unsuitable_advantages"
case unsuitableDisadvantages = "unsuitable_disadvantages"
case errata = "errata"
}
}
/// Provides examples of variants for the profession, which may include changes to AP values and additional or modified skill ratings, special abilities, or combat techniques, as compared to the basic profession. Usually picking a variant is optional, but there are some rare exceptions where picking a variant is required.
public struct ProfessionVariants: EntitySubtype {
/// If the selection of a profession variant is required.
public let isSelectionRequired: Bool
/// The list of profession variants.
public let list: [ProfessionVariant]
public init(isSelectionRequired: Bool, list: [ProfessionVariant]) {
self.isSelectionRequired = isSelectionRequired
self.list = list
}
private enum CodingKeys: String, CodingKey {
case isSelectionRequired = "is_selection_required"
case list = "list"
}
}
public struct ProfessionVariant: EntitySubtype {
/// The profession variant's identifier. An unique, increasing integer.
public let id: Int
/// The AP value you have to pay for the package variant.
public let apValue: Int
/// 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.
public let prerequisites: ProfessionPrerequisites?
public let options: ProfessionVariantPackageOptions?
/// Any special abilities the profession receives from the package variant.
public let specialAbilities: [VariantSpecialAbility]?
/// Provides ratings for the combat techniques that the hero receives from the package variant.
public let combatTechniques: [CombatTechniqueRating]?
/// The skill ratings the package variant grants to the hero.
public let skills: [SkillRating]?
/// The skill ratings a magical profession variant receives for spells; these spells are considered activated. Spells from an unfamiliar Tradition, if any, are identified as such.
public let spells: [SpellRating]?
/// Clerical professions receive these liturgical chants at the listed skill ratings. These liturgical chants are considered activated.
public let liturgicalChants: [LiturgicalChantRating]?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ProfessionVariantTranslation>
public init(id: Int, apValue: Int, prerequisites: ProfessionPrerequisites? = nil, options: ProfessionVariantPackageOptions? = nil, specialAbilities: [VariantSpecialAbility]? = nil, combatTechniques: [CombatTechniqueRating]? = nil, skills: [SkillRating]? = nil, spells: [SpellRating]? = nil, liturgicalChants: [LiturgicalChantRating]? = nil, translations: LocaleMap<ProfessionVariantTranslation>) {
self.id = id
self.apValue = apValue
self.prerequisites = prerequisites
self.options = options
self.specialAbilities = specialAbilities
self.combatTechniques = combatTechniques
self.skills = skills
self.spells = spells
self.liturgicalChants = liturgicalChants
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case apValue = "ap_value"
case prerequisites = "prerequisites"
case options = "options"
case specialAbilities = "special_abilities"
case combatTechniques = "combat_techniques"
case skills = "skills"
case spells = "spells"
case liturgicalChants = "liturgical_chants"
case translations = "translations"
}
}
public struct ProfessionVariantTranslation: EntitySubtype {
/// Name of the profession variant.
public let name: ProfessionName
/// A text that replaces the generated text for the profession variant.
public let fullText: NonEmptyString?
/// A text that is appended to the generated text for the profession variant.
///
/// Has no effect when `full_text` is set.
public let concludingText: NonEmptyString?
public init(name: ProfessionName, fullText: NonEmptyString? = nil, concludingText: NonEmptyString? = nil) {
self.name = name
self.fullText = fullText
self.concludingText = concludingText
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case fullText = "full_text"
case concludingText = "concluding_text"
}
}
public enum ProfessionSpecialAbility: EntitySubtype {
case fixed(FixedSpecialAbility)
case selection(SpecialAbilitySelection)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case fixed = "fixed"
case selection = "selection"
}
private enum Discriminator: String, Decodable {
case fixed = "Fixed"
case selection = "Selection"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .fixed:
self = .fixed(try container.decode(FixedSpecialAbility.self, forKey: .fixed))
case .selection:
self = .selection(try container.decode(SpecialAbilitySelection.self, forKey: .selection))
}
}
}
public typealias FixedSpecialAbility = SpecialAbilityDefinition
public struct SpecialAbilitySelection: EntitySubtype {
public let options: [SpecialAbilityDefinition]
public init(options: [SpecialAbilityDefinition]) {
self.options = options
}
}
public struct SpecialAbilityDefinition: EntitySubtype {
/// The identifier of the combat technique to provide the rating for.
public let id: SpecialAbilityIdentifier
/// The level of the received special ability.
public let level: Int?
/// Received select options. Order is important. Typically, you only need the first array index, though.
public let options: [RequirableSelectOptionIdentifier]?
public init(id: SpecialAbilityIdentifier, level: Int? = nil, options: [RequirableSelectOptionIdentifier]? = nil) {
self.id = id
self.level = level
self.options = options
}
}
public enum VariantSpecialAbility: EntitySubtype {
case fixed(FixedVariantSpecialAbility)
case selection(VariantSpecialAbilitySelection)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case fixed = "fixed"
case selection = "selection"
}
private enum Discriminator: String, Decodable {
case fixed = "Fixed"
case selection = "Selection"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .fixed:
self = .fixed(try container.decode(FixedVariantSpecialAbility.self, forKey: .fixed))
case .selection:
self = .selection(try container.decode(VariantSpecialAbilitySelection.self, forKey: .selection))
}
}
}
public struct FixedVariantSpecialAbility: EntitySubtype {
/// The identifier of the combat technique to provide the rating for.
public let id: SpecialAbilityIdentifier
/// if set to `false`, if the selection is granted by the basic package, it is removed.
public let active: Bool?
/// The level of the received special ability.
public let level: Int?
/// Received select options. Order is important. Typically, you only need the first array index, though.
public let options: [RequirableSelectOptionIdentifier]?
public init(id: SpecialAbilityIdentifier, active: Bool? = nil, level: Int? = nil, options: [RequirableSelectOptionIdentifier]? = nil) {
self.id = id
self.active = active
self.level = level
self.options = options
}
}
public struct VariantSpecialAbilitySelection: EntitySubtype {
/// if set to `false`, if the selection is granted by the basic package, it is removed.
public let active: Bool?
public let options: [SpecialAbilityDefinition]
public init(active: Bool? = nil, options: [SpecialAbilityDefinition]) {
self.active = active
self.options = options
}
}
public struct CombatTechniqueRating: EntitySubtype {
/// The identifier of the combat technique to provide the rating for.
public let id: CombatTechniqueIdentifier
/// The rating bonus provided for the combat technique. If used in a profession variant, it can also be used to lower the bonus of the base profession.
///
/// **Note:** This is a rating *bonus*, so it will be *added* to the default
/// value of 6.
public let rating: Int
public init(id: CombatTechniqueIdentifier, rating: Int) {
self.id = id
self.rating = rating
}
}
public struct SkillRating: EntitySubtype {
/// The identifier of the skill to provide the rating for.
public let id: SkillIdentifier
/// The rating bonus provided for the skill. If used in a profession variant, it can also be used to lower the bonus of the base profession
public let rating: Int
public init(id: SkillIdentifier, rating: Int) {
self.id = id
self.rating = rating
}
}
public struct SpellRating: EntitySubtype {
/// The identifier(s) of the spell(s) to choose from to provide the rating for. If multiple spells are provided, they must all have the same improvement cost.
public let id: [ProfessionSpellIdentifier]
/// The rating bonus provided for the (selected) spell. If used in a profession variant, it can also be used to lower the bonus of the base profession.
public let rating: Int
public init(id: [ProfessionSpellIdentifier], rating: Int) {
self.id = id
self.rating = rating
}
}
public enum ProfessionSpellIdentifier: EntitySubtype {
case spellwork(ProfessionSpellworkIdentifier)
case magicalAction(ProfessionMagicalActionIdentifier)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case spellwork = "spellwork"
case magicalAction = "magical_action"
}
private enum Discriminator: String, Decodable {
case spellwork = "Spellwork"
case magicalAction = "MagicalAction"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .spellwork:
self = .spellwork(try container.decode(ProfessionSpellworkIdentifier.self, forKey: .spellwork))
case .magicalAction:
self = .magicalAction(try container.decode(ProfessionMagicalActionIdentifier.self, forKey: .magicalAction))
}
}
}
public struct ProfessionSpellworkIdentifier: EntitySubtype {
/// The identifier of the spell to provide the rating for.
public let id: SpellworkIdentifier
/// If the spell is not part of the magical tradition required by the package, this references the magical tradition it is part of. It can also be used to define the target magical tradition of a spell if multiple magical traditions are required and the spell is available to multiple of them.
public let tradition: MagicalTraditionReference?
public init(id: SpellworkIdentifier, tradition: MagicalTraditionReference? = nil) {
self.id = id
self.tradition = tradition
}
}
public struct ProfessionMagicalActionIdentifier: EntitySubtype {
/// The identifier of the magical action to provide the rating for.
public let id: MagicalActionIdentifier
public init(id: MagicalActionIdentifier) {
self.id = id
}
}
public struct LiturgicalChantRating: EntitySubtype {
/// The identifier(s) of the liturgical chant(s) to choose from to provide the rating for. If multiple liturgical chants are provided, they must all have the same improvement cost.
public let id: [LiturgyIdentifier]
/// The rating bonus provided for the selected liturgical chant. If used in a profession variant, it can also be used to lower the bonus of the base profession.
public let rating: Int
public init(id: [LiturgyIdentifier], rating: Int) {
self.id = id
self.rating = rating
}
}
/// In some areas, the profession package grants a loose set of stats where the player must choose between different options for the profession package.
public struct ProfessionPackageOptions: EntitySubtype {
public let skillSpecialization: SkillSpecializationOptions?
public let languagesScripts: LanguagesScriptsOptions?
public let combatTechniques: CombatTechniquesOptions?
public let cantrips: CantripsOptions?
public let curses: CursesOptions?
public let terrainKnowledge: TerrainKnowledgeOptions?
public let skills: SkillsOptions?
public init(skillSpecialization: SkillSpecializationOptions? = nil, languagesScripts: LanguagesScriptsOptions? = nil, combatTechniques: CombatTechniquesOptions? = nil, cantrips: CantripsOptions? = nil, curses: CursesOptions? = nil, terrainKnowledge: TerrainKnowledgeOptions? = nil, skills: SkillsOptions? = nil) {
self.skillSpecialization = skillSpecialization
self.languagesScripts = languagesScripts
self.combatTechniques = combatTechniques
self.cantrips = cantrips
self.curses = curses
self.terrainKnowledge = terrainKnowledge
self.skills = skills
}
private enum CodingKeys: String, CodingKey {
case skillSpecialization = "skill_specialization"
case languagesScripts = "languages_scripts"
case combatTechniques = "combat_techniques"
case cantrips = "cantrips"
case curses = "curses"
case terrainKnowledge = "terrain_knowledge"
case skills = "skills"
}
}
/// In some areas, the profession package grants a loose set of stats where the player must choose between different options for the profession package. The variant may override or remove those options.
public struct ProfessionVariantPackageOptions: EntitySubtype {
public let skillSpecialization: VariantOptionAction<SkillSpecializationOptions>?
public let languagesScripts: VariantOptionAction<LanguagesScriptsOptions>?
public let combatTechniques: VariantOptionAction<CombatTechniquesOptions>?
public let cantrips: VariantOptionAction<CantripsOptions>?
public let curses: VariantOptionAction<CursesOptions>?
public let terrainKnowledge: VariantOptionAction<TerrainKnowledgeOptions>?
public let skills: VariantOptionAction<SkillsOptions>?
public init(skillSpecialization: VariantOptionAction<SkillSpecializationOptions>? = nil, languagesScripts: VariantOptionAction<LanguagesScriptsOptions>? = nil, combatTechniques: VariantOptionAction<CombatTechniquesOptions>? = nil, cantrips: VariantOptionAction<CantripsOptions>? = nil, curses: VariantOptionAction<CursesOptions>? = nil, terrainKnowledge: VariantOptionAction<TerrainKnowledgeOptions>? = nil, skills: VariantOptionAction<SkillsOptions>? = nil) {
self.skillSpecialization = skillSpecialization
self.languagesScripts = languagesScripts
self.combatTechniques = combatTechniques
self.cantrips = cantrips
self.curses = curses
self.terrainKnowledge = terrainKnowledge
self.skills = skills
}
private enum CodingKeys: String, CodingKey {
case skillSpecialization = "skill_specialization"
case languagesScripts = "languages_scripts"
case combatTechniques = "combat_techniques"
case cantrips = "cantrips"
case curses = "curses"
case terrainKnowledge = "terrain_knowledge"
case skills = "skills"
}
}
public enum VariantOptionAction<T: EntitySubtype>: EntitySubtype {
case remove
case `override`(T)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case remove = "remove"
case `override` = "override"
}
private enum Discriminator: String, Decodable {
case remove = "Remove"
case `override` = "Override"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .remove:
self = .remove
case .`override`:
self = .`override`(try container.decode(T.self, forKey: .`override`))
}
}
}
/// Select an application from a skill or from one of a list of skills where you get a skill specialization for. You can also specify a skill groups from which you can choose a skill.
public enum SkillSpecializationOptions: EntitySubtype {
case single(SingleSkillSpecializationOption)
case group(SkillGroupSkillSpecializationOption)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case single = "single"
case group = "group"
}
private enum Discriminator: String, Decodable {
case single = "Single"
case group = "Group"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .single:
self = .single(try container.decode(SingleSkillSpecializationOption.self, forKey: .single))
case .group:
self = .group(try container.decode(SkillGroupSkillSpecializationOption.self, forKey: .group))
}
}
}
public struct SingleSkillSpecializationOption: EntitySubtype {
/// Possible skills to get a skill specialization for.
public let options: [SkillReference]
public init(options: [SkillReference]) {
self.options = options
}
}
public typealias SkillGroupSkillSpecializationOption = SkillGroupReference
/// Buy languages and scripts for a specific amount of AP.
public struct LanguagesScriptsOptions: EntitySubtype {
/// The AP value you can buy languages and scripts for.
public let apValue: Int
public init(apValue: Int) {
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case apValue = "ap_value"
}
}
/// Select one or more combat techniques you get a CtR bonus for.
public struct CombatTechniquesOptions: EntitySubtype {
/// Specify the number of combat techniques that can be selected so that they get increased to a specific CtR. There can be multiple selections with different CtRs.
public let fixed: [RatingForCombatTechniquesNumber]
/// Define if after the fixed selections the remaining unselected combat techniques will receive a certain rating bonus as well.
public let restRating: Int?
/// The list of combat techniques to choose from.
public let options: [CombatTechniqueReference]
public init(fixed: [RatingForCombatTechniquesNumber], restRating: Int? = nil, options: [CombatTechniqueReference]) {
self.fixed = fixed
self.restRating = restRating
self.options = options
}
private enum CodingKeys: String, CodingKey {
case fixed = "fixed"
case restRating = "rest_rating"
case options = "options"
}
}
public struct RatingForCombatTechniquesNumber: EntitySubtype {
/// The number of selectable combat techniques.
public let number: Int
/// The rating bonus provided for the selected combat technique(s).
///
/// **Note:** This is a rating *bonus*, so it will be *added* to the default
/// value of 6.
public let rating: Int
public init(number: Int, rating: Int) {
self.number = number
self.rating = rating
}
}
/// Select one or more cantrips you receive.
public struct CantripsOptions: EntitySubtype {
/// The number of selectable cantrips.
public let number: Int
/// The list of cantrips to choose from.
public let options: [CantripReference]
public init(number: Int, options: [CantripReference]) {
self.number = number
self.options = options
}
}
/// Buy curses for a specific amount of AP.
public struct CursesOptions: EntitySubtype {
/// The AP value you can buy curses for.
public let apValue: Int
public init(apValue: Int) {
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case apValue = "ap_value"
}
}
/// Select one of a list of possible terrain knowledges
public struct TerrainKnowledgeOptions: EntitySubtype {
/// The list of possible terrain knowledges.
public let options: [TerrainKnowledgeOptionReference]
public init(options: [TerrainKnowledgeOptionReference]) {
self.options = options
}
}
public struct TerrainKnowledgeOptionReference: EntitySubtype {
/// The terrain knowledge option's identifier.
public let id: Int
public init(id: Int) {
self.id = id
}
}
/// Buy skills for a specific amount of AP.
public struct SkillsOptions: EntitySubtype {
/// If specified, you may only choose from skills of the specified group.
public let group: SkillGroupReference?
/// The AP value you can buy skills for.
public let apValue: Int
public init(group: SkillGroupReference? = nil, apValue: Int) {
self.group = group
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case group = "group"
case apValue = "ap_value"
}
}
/// The name of the profession that may have sex-specific names.
public enum ProfessionName: EntitySubtype {
case simpleProfessionName(SimpleProfessionName)
case professionNameBySex(ProfessionNameBySex)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let simpleProfessionName = try? container.decode(SimpleProfessionName.self) {
self = .simpleProfessionName(simpleProfessionName)
} else if let professionNameBySex = try? container.decode(ProfessionNameBySex.self) {
self = .professionNameBySex(professionNameBySex)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No ProfessionName type found")
}
}
}
public typealias SimpleProfessionName = NonEmptyString
public struct ProfessionNameBySex: EntitySubtype {
/// The name from the source publication.
public let `default`: NonEmptyString
/// The male name.
public let male: NonEmptyString
/// The female name.
public let female: NonEmptyString
public init(`default`: NonEmptyString, male: NonEmptyString, female: NonEmptyString) {
self.`default` = `default`
self.male = male
self.female = female
}
private enum CodingKeys: String, CodingKey {
case `default` = "default"
case male = "male"
case female = "female"
}
}
@@ -0,0 +1,30 @@
//
// Property.swift
// OptolithDatabaseSchema
//
public struct Property: LocalizableEntity {
/// The property's identifier. An unique, increasing integer.
public let id: Int
/// The property check's attributes. Only the properties from the Core Rules have defined property checks.
public let check: SkillCheck?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PropertyTranslation>
public init(id: Int, check: SkillCheck? = nil, translations: LocaleMap<PropertyTranslation>) {
self.id = id
self.check = check
self.translations = translations
}
}
public struct PropertyTranslation: EntitySubtype {
/// The property's name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,354 @@
//
// Race.swift
// OptolithDatabaseSchema
//
/// A playable race with stats and skills.
public struct Race: LocalizableEntity {
/// The race's identifier. An unique, increasing integer.
public let id: Int
/// How many Adventure Points does the race cost during character creation?
public let apValue: Int
/// The races base values.
public let baseValues: BaseValues
/// Describes how to raise or lower maximum attribute values during character creation.
public let attributeAdjustments: AttributeAdjustments
/// A list of automatically applied advantages. This does only work for advantages with no further configuration such as level or special selection.
public let automaticAdvantages: [AdvantageReference]?
/// A list of strongly recommended advantages.
public let stronglyRecommendedAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// A list of strongly recommended disadvantages.
public let stronglyRecommendedDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// Configuration for random weight generation.
public let weight: RandomWeightGeneration
/// Defines the starting ages for the race. It depends on the selected experience level.
public let startingAge: [StartingAgeConfigForExperienceLevel]
/// 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.
public let variants: RaceVariants
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RaceTranslation>
public init(id: Int, apValue: Int, baseValues: BaseValues, attributeAdjustments: AttributeAdjustments, automaticAdvantages: [AdvantageReference]? = nil, stronglyRecommendedAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, stronglyRecommendedDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, weight: RandomWeightGeneration, startingAge: [StartingAgeConfigForExperienceLevel], variants: RaceVariants, src: PublicationRefs, translations: LocaleMap<RaceTranslation>) {
self.id = id
self.apValue = apValue
self.baseValues = baseValues
self.attributeAdjustments = attributeAdjustments
self.automaticAdvantages = automaticAdvantages
self.stronglyRecommendedAdvantages = stronglyRecommendedAdvantages
self.stronglyRecommendedDisadvantages = stronglyRecommendedDisadvantages
self.weight = weight
self.startingAge = startingAge
self.variants = variants
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case apValue = "ap_value"
case baseValues = "base_values"
case attributeAdjustments = "attribute_adjustments"
case automaticAdvantages = "automatic_advantages"
case stronglyRecommendedAdvantages = "strongly_recommended_advantages"
case stronglyRecommendedDisadvantages = "strongly_recommended_disadvantages"
case weight = "weight"
case startingAge = "starting_age"
case variants = "variants"
case src = "src"
case translations = "translations"
}
}
/// The races base values.
public struct BaseValues: EntitySubtype {
/// The races life point base value.
public let lifePoints: Int
/// The races Spirit base value.
public let spirit: Int
/// The races Toughness base value.
public let toughness: Int
/// The races tactical movement rate.
public let movement: Int
public init(lifePoints: Int, spirit: Int, toughness: Int, movement: Int) {
self.lifePoints = lifePoints
self.spirit = spirit
self.toughness = toughness
self.movement = movement
}
private enum CodingKeys: String, CodingKey {
case lifePoints = "life_points"
case spirit = "spirit"
case toughness = "toughness"
case movement = "movement"
}
}
/// Describes how to raise or lower maximum attribute values during character creation.
public struct AttributeAdjustments: EntitySubtype {
/// The values by which the maximum of the respective attribute is modified.
public let fixed: [FixedAttributeAdjustment]?
/// An array of attribute maximum modifiers, where the attribute they apply to is selected from a list of options.
///
/// The array only permits a single entry because no race specified more than one selectable attribute adjustment so far. But the schema allows for multiple entries to be future-proof.
public let selectable: [SelectableAttributeAdjustment]?
public init(fixed: [FixedAttributeAdjustment]? = nil, selectable: [SelectableAttributeAdjustment]? = nil) {
self.fixed = fixed
self.selectable = selectable
}
}
/// A value by which the maximum of the respective attribute is modified.
public struct FixedAttributeAdjustment: EntitySubtype {
/// The attribute the modifier applies to.
public let id: AttributeIdentifier
/// The value by which the specified attribute's maximum is modified
/// (negative values will lower the maximum).
public let value: Int
public init(id: AttributeIdentifier, value: Int) {
self.id = id
self.value = value
}
}
/// A value that will be added to the current maximum of the selected attribute that has been chosen from the listed attributes (negative values will lower the maximum).
public struct SelectableAttributeAdjustment: EntitySubtype {
/// A list of attributes the player has to choose from.
public let list: [AttributeReference]
/// The value by which the selected attribute's maximum is modified
/// (negative values will lower the maximum).
public let value: Int
public init(list: [AttributeReference], value: Int) {
self.list = list
self.value = value
}
}
/// Configuration for random weight generation.
public struct RandomWeightGeneration: EntitySubtype {
/// The base value used for random weight. The height subtrahend; in case of
/// `Height - 110 + 2D6` it is `110`.
public let base: Int
/// The dice used for random weight.
public let random: [WeightDice]
public init(base: Int, random: [WeightDice]) {
self.base = base
self.random = random
}
}
public struct WeightDice: EntitySubtype {
/// Number of dice of the same type. Example: 2 in 2D6.
public let number: Int
/// Number of sides on every die. Example: 6 in 2D6.
public let sides: DieType
/// The strategy how to offset the randomly generated values against the base value. Either they are all added or subtracted or even results are added and odd results are subtracted.
public let offsetStrategy: WeightDiceOffsetStrategy
public init(number: Int, sides: DieType, offsetStrategy: WeightDiceOffsetStrategy) {
self.number = number
self.sides = sides
self.offsetStrategy = offsetStrategy
}
private enum CodingKeys: String, CodingKey {
case number = "number"
case sides = "sides"
case offsetStrategy = "offset_strategy"
}
}
/// The strategy how to offset the randomly generated values against the base value. Either they are all added or subtracted or even results are added and odd results are subtracted.
public enum WeightDiceOffsetStrategy: String, EntitySubtype {
case add = "Add"
case subtract = "Subtract"
case addEvenSubtractOdd = "AddEvenSubtractOdd"
}
public struct StartingAgeConfigForExperienceLevel: EntitySubtype {
/// The selected experience level's identifier.
public let id: ExperienceLevelIdentifier
/// The base value for the selected experience level.
public let base: Int
/// The random value for the selected experience level. It is going to be added to the base value.
public let random: Dice
public init(id: ExperienceLevelIdentifier, base: Int, random: Dice) {
self.id = id
self.base = base
self.random = random
}
}
/// 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.
public typealias RaceVariants = [RaceVariant]
public struct RaceVariant: EntitySubtype {
/// An unique, increasing integer.
public let id: Int
/// The list of common cultures.
public let commonCultures: [CultureReference]
/// A list of common advantages.
public let commonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// A list of common disadvantages.
public let commonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// A list of uncommon advantages.
public let uncommonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]?
/// A list of uncommon disadvantages.
public let uncommonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]?
/// An array containing 20 (numeric) hair color identifiers. The array also represents the 20-sided die for a random hair color.
public let hairColor: [HairColorReference]
/// An array containing 20 (numeric) eye color identifiers. The array also represents the 20-sided die for a random eye color.
public let eyeColor: [EyeColorReference]
/// Configuration for random height generation.
public let height: Height
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RaceVariantTranslation>
public init(id: Int, commonCultures: [CultureReference], commonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, commonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, uncommonAdvantages: [CommonnessRatedAdvantageDisadvantage<AdvantageIdentifier>]? = nil, uncommonDisadvantages: [CommonnessRatedAdvantageDisadvantage<DisadvantageIdentifier>]? = nil, hairColor: [HairColorReference], eyeColor: [EyeColorReference], height: Height, translations: LocaleMap<RaceVariantTranslation>) {
self.id = id
self.commonCultures = commonCultures
self.commonAdvantages = commonAdvantages
self.commonDisadvantages = commonDisadvantages
self.uncommonAdvantages = uncommonAdvantages
self.uncommonDisadvantages = uncommonDisadvantages
self.hairColor = hairColor
self.eyeColor = eyeColor
self.height = height
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case commonCultures = "common_cultures"
case commonAdvantages = "common_advantages"
case commonDisadvantages = "common_disadvantages"
case uncommonAdvantages = "uncommon_advantages"
case uncommonDisadvantages = "uncommon_disadvantages"
case hairColor = "hair_color"
case eyeColor = "eye_color"
case height = "height"
case translations = "translations"
}
}
/// Configuration for random height generation.
public struct Height: EntitySubtype {
/// The base value used for random height.
public let base: Int
/// The dice used for random height.
public let random: [Dice]
public init(base: Int, random: [Dice]) {
self.base = base
self.random = random
}
}
public struct RaceVariantTranslation: EntitySubtype {
/// The race variant's name. If left empty, it defaults to the base race name. This can be used if the race has no (visible) variants so that a single variant has to be provided.
public let name: NonEmptyString?
/// The respective common advantages text from the source book.
public let commonAdvantages: NonEmptyString?
/// The respective common disadvantages text from the source book.
public let commonDisadvantages: NonEmptyString?
/// The respective uncommon advantages text from the source book.
public let uncommonAdvantages: NonEmptyString?
/// The respective uncommon disadvantages text from the source book.
public let uncommonDisadvantages: NonEmptyString?
public init(name: NonEmptyString? = nil, commonAdvantages: NonEmptyString? = nil, commonDisadvantages: NonEmptyString? = nil, uncommonAdvantages: NonEmptyString? = nil, uncommonDisadvantages: NonEmptyString? = nil) {
self.name = name
self.commonAdvantages = commonAdvantages
self.commonDisadvantages = commonDisadvantages
self.uncommonAdvantages = uncommonAdvantages
self.uncommonDisadvantages = uncommonDisadvantages
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case commonAdvantages = "common_advantages"
case commonDisadvantages = "common_disadvantages"
case uncommonAdvantages = "uncommon_advantages"
case uncommonDisadvantages = "uncommon_disadvantages"
}
}
public struct RaceTranslation: EntitySubtype {
/// The race's name.
public let name: NonEmptyString
/// The respective attribute adjustments text from the source book.
public let attributeAdjustments: NonEmptyString
/// The respective automatic advantages text from the source book.
public let automaticAdvantages: NonEmptyString?
/// The respective strongly recommended advantages text from the source book.
public let stronglyRecommendedAdvantages: NonEmptyString?
/// The respective strongly recommended disadvantages text from the source book.
public let stronglyRecommendedDisadvantages: NonEmptyString?
public let errata: Errata?
public init(name: NonEmptyString, attributeAdjustments: NonEmptyString, automaticAdvantages: NonEmptyString? = nil, stronglyRecommendedAdvantages: NonEmptyString? = nil, stronglyRecommendedDisadvantages: NonEmptyString? = nil, errata: Errata? = nil) {
self.name = name
self.attributeAdjustments = attributeAdjustments
self.automaticAdvantages = automaticAdvantages
self.stronglyRecommendedAdvantages = stronglyRecommendedAdvantages
self.stronglyRecommendedDisadvantages = stronglyRecommendedDisadvantages
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case attributeAdjustments = "attribute_adjustments"
case automaticAdvantages = "automatic_advantages"
case stronglyRecommendedAdvantages = "strongly_recommended_advantages"
case stronglyRecommendedDisadvantages = "strongly_recommended_disadvantages"
case errata = "errata"
}
}
@@ -0,0 +1,26 @@
//
// Region.swift
// OptolithDatabaseSchema
//
public struct Region: LocalizableEntity {
/// The region's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RegionTranslation>
public init(id: Int, translations: LocaleMap<RegionTranslation>) {
self.id = id
self.translations = translations
}
}
public struct RegionTranslation: EntitySubtype {
/// The region name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,116 @@
//
// Ritual.swift
// OptolithDatabaseSchema
//
public struct Ritual: LocalizableEntity {
/// The ritual's identifier. An unique, increasing integer.
public let id: Int
/// Lists the linked three attributes used to make a skill check.
public let check: SkillCheck
/// In some cases, the target's Spirit or Toughness is applied as a penalty.
public let checkPenalty: SkillCheckPenalty?
/// Measurable parameters of a ritual.
public let parameters: SlowPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
/// The associated property.
public let property: PropertyReference
/// The tradition(s) the ritual is available for. It may be *generally* available to all traditions or it may be only familiar in specific traditions.
public let traditions: Traditions
/// States which column is used to improve the skill.
public let improvementCost: ImprovementCost
public let prerequisites: SpellworkPrerequisites?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RitualTranslation>
public let enhancements: Enhancements?
public init(id: Int, check: SkillCheck, checkPenalty: SkillCheckPenalty? = nil, parameters: SlowPerformanceParameters, `target`: AffectedTargetCategories, property: PropertyReference, traditions: Traditions, improvementCost: ImprovementCost, prerequisites: SpellworkPrerequisites? = nil, src: PublicationRefs, translations: LocaleMap<RitualTranslation>, enhancements: Enhancements? = nil) {
self.id = id
self.check = check
self.checkPenalty = checkPenalty
self.parameters = parameters
self.`target` = `target`
self.property = property
self.traditions = traditions
self.improvementCost = improvementCost
self.prerequisites = prerequisites
self.src = src
self.translations = translations
self.enhancements = enhancements
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case check = "check"
case checkPenalty = "check_penalty"
case parameters = "parameters"
case `target` = "target"
case property = "property"
case traditions = "traditions"
case improvementCost = "improvement_cost"
case prerequisites = "prerequisites"
case src = "src"
case translations = "translations"
case enhancements = "enhancements"
}
}
public struct RitualTranslation: EntitySubtype {
/// The name of the ritual.
public let name: String
/// The effect description may be either a plain text or a text that is divided by a list of effects for each quality level. It may also be a list for each two quality levels.
public let effect: ActivatableSkillEffect
@available(*, deprecated)
public let castingTime: OldParameter
@available(*, deprecated)
public let cost: OldParameter
@available(*, deprecated)
public let range: OldParameter
@available(*, deprecated)
public let duration: OldParameter
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: String, effect: ActivatableSkillEffect, castingTime: OldParameter, cost: OldParameter, range: OldParameter, duration: OldParameter, `target`: String, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case effect = "effect"
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
@@ -0,0 +1,52 @@
//
// Service.swift
// OptolithDatabaseSchema
//
public struct Service: LocalizableEntity {
/// The service's identifier. An unique, increasing integer.
public let id: Int
/// Defines for which creature type(s) the service is available.
public let `availability`: [ServiceAvailability]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ServiceTranslation>
public init(id: Int, `availability`: [ServiceAvailability], src: PublicationRefs, translations: LocaleMap<ServiceTranslation>) {
self.id = id
self.`availability` = `availability`
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case `availability` = "availability"
case src = "src"
case translations = "translations"
}
}
public enum ServiceAvailability: String, EntitySubtype {
case summonedCreatures = "SummonedCreatures"
case monstrosities = "Monstrosities"
}
public struct ServiceTranslation: EntitySubtype {
/// The name of the service.
public let name: NonEmptyString
/// The description of the service.
public let description: NonEmptyMarkdown
public let errata: Errata?
public init(name: NonEmptyString, description: NonEmptyMarkdown, errata: Errata? = nil) {
self.name = name
self.description = description
self.errata = errata
}
}
@@ -0,0 +1,45 @@
//
// SexPractice.swift
// OptolithDatabaseSchema
//
public struct SexPractice: LocalizableEntity {
/// The sex practice's identifier. An unique, increasing integer.
public let id: Int
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SexPracticeTranslation>
public init(id: Int, src: PublicationRefs, translations: LocaleMap<SexPracticeTranslation>) {
self.id = id
self.src = src
self.translations = translations
}
}
public struct SexPracticeTranslation: EntitySubtype {
/// The sex practice's name.
public let name: NonEmptyString
/// The rules of the sex practice.
public let rules: NonEmptyString
/// How long a round of this sex practice takes.
public let duration: NonEmptyString
/// Prerequisites of participants and environment. Do not specify if the sex practice has no prerequisites.
public let prerequisites: NonEmptyString?
/// Effects of a failed *Seduction* check.
public let failed: NonEmptyString
public init(name: NonEmptyString, rules: NonEmptyString, duration: NonEmptyString, prerequisites: NonEmptyString? = nil, failed: NonEmptyString) {
self.name = name
self.rules = rules
self.duration = duration
self.prerequisites = prerequisites
self.failed = failed
}
}
@@ -0,0 +1,171 @@
//
// Skill.swift
// OptolithDatabaseSchema
//
public struct Skill: LocalizableEntity {
/// The skill's identifier. An unique, increasing integer.
public let id: Int
/// Lists the linked three attributes used to make a skill check.
public let check: SkillCheck
/// Lists applications for the skill, if any. This does not necessarily include all possible applications. There may also be new applications that could be purchased via certain advantages or special abilities.
public let applications: Applications
/// Indicates whether encumbrance gives a penalty for checks with the skill.
public let encumbrance: EncumbranceInfluence
/// States which column is used to improve the skill.
public let improvementCost: ImprovementCost
/// The skill group this skill belongs to.
public let group: SkillGroupReference
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SkillTranslation>
public init(id: Int, check: SkillCheck, applications: Applications, encumbrance: EncumbranceInfluence, improvementCost: ImprovementCost, group: SkillGroupReference, src: PublicationRefs, translations: LocaleMap<SkillTranslation>) {
self.id = id
self.check = check
self.applications = applications
self.encumbrance = encumbrance
self.improvementCost = improvementCost
self.group = group
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case check = "check"
case applications = "applications"
case encumbrance = "encumbrance"
case improvementCost = "improvement_cost"
case group = "group"
case src = "src"
case translations = "translations"
}
}
public struct SkillTranslation: EntitySubtype {
/// The name of the skill.
public let name: NonEmptyString
/// If there are options available that can not be put into a selection list (like different cults), provide the label text for the input element here. Otherwise leave empty.
public let applicationsInputLabel: NonEmptyString?
/// The text listing the certain circumstances in which the encumbrance may count. This text must be used if `encumbrance` is set to `"Maybe"`, otherwise it is ignored if defined.
public let encumbranceDescription: NonEmptyString?
/// Mentions any tools from the equipment list that are necessary to employ the skill.
public let tools: NonEmptyMarkdown?
/// Gives examples of the effects that various QL might provide.
public let quality: NonEmptyMarkdown
/// Lists examples of results for a failed check.
public let failed: NonEmptyMarkdown
/// Lists examples of results for a critical success.
public let critical: NonEmptyMarkdown
/// Lists examples of results for botches.
public let botch: NonEmptyMarkdown
public let errata: Errata?
public init(name: NonEmptyString, applicationsInputLabel: NonEmptyString? = nil, encumbranceDescription: NonEmptyString? = nil, tools: NonEmptyMarkdown? = nil, quality: NonEmptyMarkdown, failed: NonEmptyMarkdown, critical: NonEmptyMarkdown, botch: NonEmptyMarkdown, errata: Errata? = nil) {
self.name = name
self.applicationsInputLabel = applicationsInputLabel
self.encumbranceDescription = encumbranceDescription
self.tools = tools
self.quality = quality
self.failed = failed
self.critical = critical
self.botch = botch
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case applicationsInputLabel = "applications_input_label"
case encumbranceDescription = "encumbrance_description"
case tools = "tools"
case quality = "quality"
case failed = "failed"
case critical = "critical"
case botch = "botch"
case errata = "errata"
}
}
/// A category. All available entries from the specified category will be included as separate applications.
public enum ApplicationCategory: String, EntitySubtype {
case blessedTraditions = "BlessedTraditions"
case diseases = "Diseases"
case regions = "Regions"
}
/// The skill's applications.
public enum Applications: EntitySubtype {
case derived(ApplicationCategory)
case explicit(SpecificApplications)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case derived = "derived"
case explicit = "explicit"
}
private enum Discriminator: String, Decodable {
case derived = "Derived"
case explicit = "Explicit"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .derived:
self = .derived(try container.decode(ApplicationCategory.self, forKey: .derived))
case .explicit:
self = .explicit(try container.decode(SpecificApplications.self, forKey: .explicit))
}
}
}
/// A list of explicit applications.
public typealias SpecificApplications = [Application]
/// An explicit skill application.
public struct Application: EntitySubtype {
/// The skill application's identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ApplicationTranslation>
public init(id: Int, translations: LocaleMap<ApplicationTranslation>) {
self.id = id
self.translations = translations
}
}
public struct ApplicationTranslation: EntitySubtype {
/// The skill application's name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
/// Indicates whether encumbrance gives a penalty for checks with the skill.
public enum EncumbranceInfluence: String, EntitySubtype {
case `true` = "True"
case `false` = "False"
case maybe = "Maybe"
}
@@ -0,0 +1,39 @@
//
// SkillGroup.swift
// OptolithDatabaseSchema
//
public struct SkillGroup: LocalizableEntity {
/// The skill group's identifier. An unique, increasing integer.
public let id: Int
/// The skill group check's attributes.
public let check: SkillCheck
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SkillGroupTranslation>
public init(id: Int, check: SkillCheck, translations: LocaleMap<SkillGroupTranslation>) {
self.id = id
self.check = check
self.translations = translations
}
}
public struct SkillGroupTranslation: EntitySubtype {
/// The skill group's name.
public let name: NonEmptyString
/// The skill group's long name.
public let longName: NonEmptyString
public init(name: NonEmptyString, longName: NonEmptyString) {
self.name = name
self.longName = longName
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case longName = "long_name"
}
}
@@ -0,0 +1,111 @@
//
// SkillModificationLevel.swift
// OptolithDatabaseSchema
//
public struct SkillModificationLevel: Entity {
/// The skill modification level's identifier.
public let id: Int
/// Configuration for this level for fast skills (spells, liturgical chants).
public let fast: FastSkillModificationLevelConfig
/// Configuration for this level for slow skills (rituals, ceremonies).
public let slow: SlowSkillModificationLevelConfig
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SkillModificationLevelTranslation>?
public init(id: Int, fast: FastSkillModificationLevelConfig, slow: SlowSkillModificationLevelConfig, translations: LocaleMap<SkillModificationLevelTranslation>? = nil) {
self.id = id
self.fast = fast
self.slow = slow
self.translations = translations
}
}
public struct FastSkillModificationLevelConfig: EntitySubtype {
/// The casting time in actions.
public let castingTime: Int
/// The range in meters.
public let range: Int
/// The cost in AE/KP.
public let cost: Int
public init(castingTime: Int, range: Int, cost: Int) {
self.castingTime = castingTime
self.range = range
self.cost = cost
}
private enum CodingKeys: String, CodingKey {
case castingTime = "casting_time"
case range = "range"
case cost = "cost"
}
}
public struct SlowSkillModificationLevelConfig: EntitySubtype {
/// The casting time.
public let castingTime: SlowSkillCastingTime
/// The range in meters.
public let range: Int
/// The cost in AE/KP.
public let cost: Int
public init(castingTime: SlowSkillCastingTime, range: Int, cost: Int) {
self.castingTime = castingTime
self.range = range
self.cost = cost
}
private enum CodingKeys: String, CodingKey {
case castingTime = "casting_time"
case range = "range"
case cost = "cost"
}
}
public struct SlowSkillCastingTime: EntitySubtype {
/// The (unitless) casting time.
public let value: Int
/// The unit for the `value`.
public let unit: SlowSkillCastingTimeUnit
public init(value: Int, unit: SlowSkillCastingTimeUnit) {
self.value = value
self.unit = unit
}
}
public enum SlowSkillCastingTimeUnit: String, EntitySubtype {
case minutes = "Minutes"
case hours = "Hours"
}
public struct SkillModificationLevelTranslation: EntitySubtype {
/// Configuration for this level for fast skills (spells, liturgical chants). Values set here override the default generated text.
public let fast: LevelTypeConfigTranslation?
/// Configuration for this level for slow skills (rituals, ceremonies). Values set here override the default generated text.
public let slow: LevelTypeConfigTranslation?
public init(fast: LevelTypeConfigTranslation? = nil, slow: LevelTypeConfigTranslation? = nil) {
self.fast = fast
self.slow = slow
}
}
/// Configuration translation of a type for a level. Values set here override the default generated text.
public struct LevelTypeConfigTranslation: EntitySubtype {
public let range: NonEmptyString
public init(range: NonEmptyString) {
self.range = range
}
}
@@ -0,0 +1,26 @@
//
// SocialStatus.swift
// OptolithDatabaseSchema
//
public struct SocialStatus: LocalizableEntity {
/// The social status' identifier. An unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SocialStatusTranslation>
public init(id: Int, translations: LocaleMap<SocialStatusTranslation>) {
self.id = id
self.translations = translations
}
}
public struct SocialStatusTranslation: EntitySubtype {
/// The social status name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,116 @@
//
// Spell.swift
// OptolithDatabaseSchema
//
public struct Spell: LocalizableEntity {
/// The spell's identifier. An unique, increasing integer.
public let id: Int
/// Lists the linked three attributes used to make a skill check.
public let check: SkillCheck
/// In some cases, the target's Spirit or Toughness is applied as a penalty.
public let checkPenalty: SkillCheckPenalty?
/// Measurable parameters of a spell.
public let parameters: FastPerformanceParameters
/// The target category the kind of creature or object the skill affects.
public let `target`: AffectedTargetCategories
/// The associated property.
public let property: PropertyReference
/// The tradition(s) the spell is available for. It may be *generally* available to all traditions or it may be only familiar in specific traditions.
public let traditions: Traditions
/// States which column is used to improve the skill.
public let improvementCost: ImprovementCost
public let prerequisites: SpellworkPrerequisites?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SpellTranslation>
public let enhancements: Enhancements?
public init(id: Int, check: SkillCheck, checkPenalty: SkillCheckPenalty? = nil, parameters: FastPerformanceParameters, `target`: AffectedTargetCategories, property: PropertyReference, traditions: Traditions, improvementCost: ImprovementCost, prerequisites: SpellworkPrerequisites? = nil, src: PublicationRefs, translations: LocaleMap<SpellTranslation>, enhancements: Enhancements? = nil) {
self.id = id
self.check = check
self.checkPenalty = checkPenalty
self.parameters = parameters
self.`target` = `target`
self.property = property
self.traditions = traditions
self.improvementCost = improvementCost
self.prerequisites = prerequisites
self.src = src
self.translations = translations
self.enhancements = enhancements
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case check = "check"
case checkPenalty = "check_penalty"
case parameters = "parameters"
case `target` = "target"
case property = "property"
case traditions = "traditions"
case improvementCost = "improvement_cost"
case prerequisites = "prerequisites"
case src = "src"
case translations = "translations"
case enhancements = "enhancements"
}
}
public struct SpellTranslation: EntitySubtype {
/// The name of the spell.
public let name: NonEmptyString
/// The effect description may be either a plain text or a text that is divided by a list of effects for each quality level. It may also be a list for each two quality levels.
public let effect: ActivatableSkillEffect
@available(*, deprecated)
public let castingTime: OldParameter
@available(*, deprecated)
public let cost: OldParameter
@available(*, deprecated)
public let range: OldParameter
@available(*, deprecated)
public let duration: OldParameter
@available(*, deprecated)
public let `target`: String
public let errata: Errata?
public init(name: NonEmptyString, effect: ActivatableSkillEffect, castingTime: OldParameter, cost: OldParameter, range: OldParameter, duration: OldParameter, `target`: String, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
self.`target` = `target`
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case effect = "effect"
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
case `target` = "target"
case errata = "errata"
}
}
@@ -0,0 +1,36 @@
//
// State.swift
// OptolithDatabaseSchema
//
public struct State: LocalizableEntity {
/// The state's identifier. An unique, increasing integer.
public let id: Int
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<StateTranslation>
public init(id: Int, src: PublicationRefs, translations: LocaleMap<StateTranslation>) {
self.id = id
self.src = src
self.translations = translations
}
}
public struct StateTranslation: EntitySubtype {
/// The name of the state.
public let name: NonEmptyString
/// The description of the state.
public let description: NonEmptyMarkdown
public let errata: Errata?
public init(name: NonEmptyString, description: NonEmptyMarkdown, errata: Errata? = nil) {
self.name = name
self.description = description
self.errata = errata
}
}
@@ -0,0 +1,82 @@
//
// Talisman.swift
// OptolithDatabaseSchema
//
public struct Talisman: LocalizableEntity {
/// The talisman's identifier. An unique, increasing integer.
public let id: Int
/// The tradition(s) the talisman belongs to.
public let tradition: [BlessedTraditionReference]
/// The talisman type, if any.
public let type: TalismanType?
/// The AP value for the required trade secret, if possible.
public let apValue: Int?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<TalismanTranslation>
public init(id: Int, tradition: [BlessedTraditionReference], type: TalismanType? = nil, apValue: Int? = nil, src: PublicationRefs, translations: LocaleMap<TalismanTranslation>) {
self.id = id
self.tradition = tradition
self.type = type
self.apValue = apValue
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case tradition = "tradition"
case type = "type"
case apValue = "ap_value"
case src = "src"
case translations = "translations"
}
}
public enum TalismanType: String, EntitySubtype {
case mainTalisman = "MainTalisman"
case talisman = "Talisman"
case minorTalisman = "MinorTalisman"
case regalia = "Regalia"
case powerfulTalisman = "PowerfulTalisman"
}
public struct TalismanTranslation: EntitySubtype {
/// The name of the talisman.
public let name: NonEmptyString
/// The effect description.
public let effect: NonEmptyMarkdown
/// The activation parameters.
public let activation: TalismanActivationTranslation?
public let errata: Errata?
public init(name: NonEmptyString, effect: NonEmptyMarkdown, activation: TalismanActivationTranslation? = nil, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.activation = activation
self.errata = errata
}
}
public struct TalismanActivationTranslation: EntitySubtype {
/// The KP cost.
public let cost: Int
/// The duration.
public let duration: NonEmptyString
public init(cost: Int, duration: NonEmptyString) {
self.cost = cost
self.duration = duration
}
}
@@ -0,0 +1,40 @@
//
// TargetCategory.swift
// OptolithDatabaseSchema
//
public struct TargetCategory: LocalizableEntity {
/// The target category's identifier. An unique, increasing integer.
public let id: Int
/// A superordinate target category, if present.
public let parent: TargetCategoryParent?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<TargetCategoryTranslation>
public init(id: Int, parent: TargetCategoryParent? = nil, translations: LocaleMap<TargetCategoryTranslation>) {
self.id = id
self.parent = parent
self.translations = translations
}
}
/// A superordinate target category, if present.
public struct TargetCategoryParent: EntitySubtype {
/// The identifier of the superordinate target category.
public let id: TargetCategoryIdentifier
public init(id: TargetCategoryIdentifier) {
self.id = id
}
}
public struct TargetCategoryTranslation: EntitySubtype {
/// The target category name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,594 @@
//
// _ActivatableSelectOptionCategory.swift
// OptolithDatabaseSchema
//
public enum SelectOptionCategory: EntitySubtype {
case blessings
case cantrips
case tradeSecrets
case scripts
case animalShapes
case arcaneBardTraditions
case arcaneDancerTraditions
case sexPractices
case races
case cultures
case racesAndCultures
case blessedTraditions(BlessedTraditionsSelectOptionCategory)
case elements(ElementsSelectOptionCategory)
case properties(PropertiesSelectOptionCategory)
case aspects(AspectSelectOptionCategory)
case diseases(DiseasesPoisonsSelectOptionCategory)
case poisons(DiseasesPoisonsSelectOptionCategory)
case languages(LanguagesSelectOptionCategory)
case skills(SkillsSelectOptionCategory)
case combatTechniques(CombatTechniquesSelectOptionCategory)
case targetCategories(TargetCategoriesSelectOptionCategory)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case blessings = "blessings"
case cantrips = "cantrips"
case tradeSecrets = "trade_secrets"
case scripts = "scripts"
case animalShapes = "animal_shapes"
case arcaneBardTraditions = "arcane_bard_traditions"
case arcaneDancerTraditions = "arcane_dancer_traditions"
case sexPractices = "sex_practices"
case races = "races"
case cultures = "cultures"
case racesAndCultures = "races_and_cultures"
case blessedTraditions = "blessed_traditions"
case elements = "elements"
case properties = "properties"
case aspects = "aspects"
case diseases = "diseases"
case poisons = "poisons"
case languages = "languages"
case skills = "skills"
case combatTechniques = "combat_techniques"
case targetCategories = "target_categories"
}
private enum Discriminator: String, Decodable {
case blessings = "Blessings"
case cantrips = "Cantrips"
case tradeSecrets = "TradeSecrets"
case scripts = "Scripts"
case animalShapes = "AnimalShapes"
case arcaneBardTraditions = "ArcaneBardTraditions"
case arcaneDancerTraditions = "ArcaneDancerTraditions"
case sexPractices = "SexPractices"
case races = "Races"
case cultures = "Cultures"
case racesAndCultures = "RacesAndCultures"
case blessedTraditions = "BlessedTraditions"
case elements = "Elements"
case properties = "Properties"
case aspects = "Aspects"
case diseases = "Diseases"
case poisons = "Poisons"
case languages = "Languages"
case skills = "Skills"
case combatTechniques = "CombatTechniques"
case targetCategories = "TargetCategories"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .blessings:
self = .blessings
case .cantrips:
self = .cantrips
case .tradeSecrets:
self = .tradeSecrets
case .scripts:
self = .scripts
case .animalShapes:
self = .animalShapes
case .arcaneBardTraditions:
self = .arcaneBardTraditions
case .arcaneDancerTraditions:
self = .arcaneDancerTraditions
case .sexPractices:
self = .sexPractices
case .races:
self = .races
case .cultures:
self = .cultures
case .racesAndCultures:
self = .racesAndCultures
case .blessedTraditions:
self = .blessedTraditions(try container.decode(BlessedTraditionsSelectOptionCategory.self, forKey: .blessedTraditions))
case .elements:
self = .elements(try container.decode(ElementsSelectOptionCategory.self, forKey: .elements))
case .properties:
self = .properties(try container.decode(PropertiesSelectOptionCategory.self, forKey: .properties))
case .aspects:
self = .aspects(try container.decode(AspectSelectOptionCategory.self, forKey: .aspects))
case .diseases:
self = .diseases(try container.decode(DiseasesPoisonsSelectOptionCategory.self, forKey: .diseases))
case .poisons:
self = .poisons(try container.decode(DiseasesPoisonsSelectOptionCategory.self, forKey: .poisons))
case .languages:
self = .languages(try container.decode(LanguagesSelectOptionCategory.self, forKey: .languages))
case .skills:
self = .skills(try container.decode(SkillsSelectOptionCategory.self, forKey: .skills))
case .combatTechniques:
self = .combatTechniques(try container.decode(CombatTechniquesSelectOptionCategory.self, forKey: .combatTechniques))
case .targetCategories:
self = .targetCategories(try container.decode(TargetCategoriesSelectOptionCategory.self, forKey: .targetCategories))
}
}
}
public struct BlessedTraditionsSelectOptionCategory: EntitySubtype {
/// Should the principles (code) of the tradition be required to select the respective tradition?
public let requirePrinciples: Bool?
public init(requirePrinciples: Bool? = nil) {
self.requirePrinciples = requirePrinciples
}
private enum CodingKeys: String, CodingKey {
case requirePrinciples = "require_principles"
}
}
public struct ElementsSelectOptionCategory: EntitySubtype {
/// Only include entries with the listed identifiers.
public let specific: [ElementReference]?
public init(specific: [ElementReference]? = nil) {
self.specific = specific
}
}
public struct PropertiesSelectOptionCategory: EntitySubtype {
/// Does each property require it's corresponding property knowledge?
public let requireKnowledge: Bool?
/// Require a minimum number of spellworks of the respective property to be on a minimum skill rating.
public let requireMinimumSpellworksOn: RequiredMinimumSkillsToBeOnSkillRating?
public init(requireKnowledge: Bool? = nil, requireMinimumSpellworksOn: RequiredMinimumSkillsToBeOnSkillRating? = nil) {
self.requireKnowledge = requireKnowledge
self.requireMinimumSpellworksOn = requireMinimumSpellworksOn
}
private enum CodingKeys: String, CodingKey {
case requireKnowledge = "require_knowledge"
case requireMinimumSpellworksOn = "require_minimum_spellworks_on"
}
}
public struct AspectSelectOptionCategory: EntitySubtype {
/// Does each aspect require it's corresponding aspect knowledge?
public let requireKnowledge: Bool?
/// The generated name should be the *Master of (Aspect)* suffix for this aspect instead of the aspect's name. If an aspect does not provide a suffix (such as the General aspect), it is automatically excluded from the list.
public let useMasterOfSuffixAsName: Bool?
/// Require a minimum number of liturgies of the respective aspect to be on a minimum skill rating.
public let requireMinimumLiturgiesOn: RequiredMinimumSkillsToBeOnSkillRating?
public init(requireKnowledge: Bool? = nil, useMasterOfSuffixAsName: Bool? = nil, requireMinimumLiturgiesOn: RequiredMinimumSkillsToBeOnSkillRating? = nil) {
self.requireKnowledge = requireKnowledge
self.useMasterOfSuffixAsName = useMasterOfSuffixAsName
self.requireMinimumLiturgiesOn = requireMinimumLiturgiesOn
}
private enum CodingKeys: String, CodingKey {
case requireKnowledge = "require_knowledge"
case useMasterOfSuffixAsName = "use_master_of_suffix_as_name"
case requireMinimumLiturgiesOn = "require_minimum_liturgies_on"
}
}
/// Require a minimum number of spellworks/liturgies of the respective property/aspect to be on a minimum skill rating.
public struct RequiredMinimumSkillsToBeOnSkillRating: EntitySubtype {
/// The minimum number of liturgies that need to be on the defined minimum skill rating.
public let number: Int
/// The minimum skill rating the defined minimum number of liturgies need to be on.
public let rating: Int
public init(number: Int, rating: Int) {
self.number = number
self.rating = rating
}
}
public struct DiseasesPoisonsSelectOptionCategory: EntitySubtype {
/// Only convert half the disease/poison level into the AP value.
public let useHalfLevelAsApValue: Bool?
public init(useHalfLevelAsApValue: Bool? = nil) {
self.useHalfLevelAsApValue = useHalfLevelAsApValue
}
private enum CodingKeys: String, CodingKey {
case useHalfLevelAsApValue = "use_half_level_as_ap_value"
}
}
public struct LanguagesSelectOptionCategory: EntitySubtype {
/// Generate prerequisites for each entry of the category.
public let prerequisites: [LanguagesSelectOptionCategoryPrerequisite]?
public init(prerequisites: [LanguagesSelectOptionCategoryPrerequisite]? = nil) {
self.prerequisites = prerequisites
}
}
public struct LanguagesSelectOptionCategoryPrerequisite: EntitySubtype {
public let tag: String
public let selectOption: OptionPrerequisite
public init(tag: String, selectOption: OptionPrerequisite) {
self.tag = tag
self.selectOption = selectOption
}
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case selectOption = "select_option"
}
}
public struct SkillsSelectOptionCategory: EntitySubtype {
/// A list of skill categories.
public let categories: [SkillsSelectOptionCategoryCategory]
/// Generate AP values for each entry.
public let apValue: SelectOptionsAdventurePointsValue<SkillishIdentifier>?
public init(categories: [SkillsSelectOptionCategoryCategory], apValue: SelectOptionsAdventurePointsValue<SkillishIdentifier>? = nil) {
self.categories = categories
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case categories = "categories"
case apValue = "ap_value"
}
}
public enum SkillsSelectOptionCategoryCategory: EntitySubtype {
case skills(SkillSelectOptionCategoryCategory)
case spells(GenericSkillsSelectOptionCategoryCategory<SpellReference>)
case rituals(GenericSkillsSelectOptionCategoryCategory<RitualReference>)
case liturgicalChants(GenericSkillsSelectOptionCategoryCategory<LiturgicalChantReference>)
case ceremonies(GenericSkillsSelectOptionCategoryCategory<CeremonyReference>)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case skills = "skills"
case spells = "spells"
case rituals = "rituals"
case liturgicalChants = "liturgical_chants"
case ceremonies = "ceremonies"
}
private enum Discriminator: String, Decodable {
case skills = "Skills"
case spells = "Spells"
case rituals = "Rituals"
case liturgicalChants = "LiturgicalChants"
case ceremonies = "Ceremonies"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .skills:
self = .skills(try container.decode(SkillSelectOptionCategoryCategory.self, forKey: .skills))
case .spells:
self = .spells(try container.decode(GenericSkillsSelectOptionCategoryCategory<SpellReference>.self, forKey: .spells))
case .rituals:
self = .rituals(try container.decode(GenericSkillsSelectOptionCategoryCategory<RitualReference>.self, forKey: .rituals))
case .liturgicalChants:
self = .liturgicalChants(try container.decode(GenericSkillsSelectOptionCategoryCategory<LiturgicalChantReference>.self, forKey: .liturgicalChants))
case .ceremonies:
self = .ceremonies(try container.decode(GenericSkillsSelectOptionCategoryCategory<CeremonyReference>.self, forKey: .ceremonies))
}
}
}
public struct SkillSelectOptionCategoryCategory: EntitySubtype {
/// Only include entries of the specified groups.
public let groups: [SkillGroupReference]?
/// Only include (`Intersection`) or exclude (`Difference`) specific skills.
public let specific: SpecificFromSkillSelectOptionCategoryCategory<SkillReference>?
/// Registers new applications, which get enabled once this entry is activated with its respective select option. It specifies an entry-unique identifier, the skill it belongs to is derived from the select option automatically. A translation can be left out if its name equals the name of the origin entry.
public let skillApplications: [SkillApplicationOrUse]?
/// Registers uses, which get enabled once this entry is activated with its respective select option. It specifies an entry-unique identifier, the skill it belongs to is derived from the select option automatically. A translation can be left out if its name equals the name of the origin entry.
public let skillUses: [SkillApplicationOrUse]?
/// Generate prerequisites for each entry of the category.
public let prerequisites: [SkillSelectOptionCategoryPrerequisite]?
/// Generate AP values for each entry.
public let apValue: SelectOptionsAdventurePointsValue<SkillIdentifier>?
public init(groups: [SkillGroupReference]? = nil, specific: SpecificFromSkillSelectOptionCategoryCategory<SkillReference>? = nil, skillApplications: [SkillApplicationOrUse]? = nil, skillUses: [SkillApplicationOrUse]? = nil, prerequisites: [SkillSelectOptionCategoryPrerequisite]? = nil, apValue: SelectOptionsAdventurePointsValue<SkillIdentifier>? = nil) {
self.groups = groups
self.specific = specific
self.skillApplications = skillApplications
self.skillUses = skillUses
self.prerequisites = prerequisites
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case groups = "groups"
case specific = "specific"
case skillApplications = "skill_applications"
case skillUses = "skill_uses"
case prerequisites = "prerequisites"
case apValue = "ap_value"
}
}
public struct CombatTechniquesSelectOptionCategory: EntitySubtype {
/// A list of combat technique categories.
public let categories: [CombatTechniquesSelectOptionCategoryCategory]
/// Generate AP values for each entry.
public let apValue: SelectOptionsAdventurePointsValue<CombatTechniqueIdentifier>?
public init(categories: [CombatTechniquesSelectOptionCategoryCategory], apValue: SelectOptionsAdventurePointsValue<CombatTechniqueIdentifier>? = nil) {
self.categories = categories
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case categories = "categories"
case apValue = "ap_value"
}
}
public enum CombatTechniquesSelectOptionCategoryCategory: EntitySubtype {
case closeCombatTechniques(GenericSkillsSelectOptionCategoryCategory<CloseCombatTechniqueReference>)
case rangedCombatTechniques(GenericSkillsSelectOptionCategoryCategory<RangedCombatTechniqueReference>)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case closeCombatTechniques = "close_combat_techniques"
case rangedCombatTechniques = "ranged_combat_techniques"
}
private enum Discriminator: String, Decodable {
case closeCombatTechniques = "CloseCombatTechniques"
case rangedCombatTechniques = "RangedCombatTechniques"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .closeCombatTechniques:
self = .closeCombatTechniques(try container.decode(GenericSkillsSelectOptionCategoryCategory<CloseCombatTechniqueReference>.self, forKey: .closeCombatTechniques))
case .rangedCombatTechniques:
self = .rangedCombatTechniques(try container.decode(GenericSkillsSelectOptionCategoryCategory<RangedCombatTechniqueReference>.self, forKey: .rangedCombatTechniques))
}
}
}
public struct SkillApplicationOrUse: EntitySubtype {
/// The application's or use's identifier. An entry-unique, increasing integer.
public let id: Int
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SkillApplicationOrUseTranslation>?
public init(id: Int, translations: LocaleMap<SkillApplicationOrUseTranslation>? = nil) {
self.id = id
self.translations = translations
}
}
public struct SkillApplicationOrUseTranslation: EntitySubtype {
/// The name of the application or use if different from the activatable entry's name.
public let name: String
public init(name: String) {
self.name = name
}
}
public struct GenericSkillsSelectOptionCategoryCategory<Ref: EntitySubtype>: EntitySubtype {
/// Only include (`Intersection`) or exclude (`Difference`) specific entries.
public let specific: SpecificFromSkillSelectOptionCategoryCategory<Ref>?
/// Generate prerequisites for each entry of the category.
public let prerequisites: [SkillSelectOptionCategoryPrerequisite]?
public init(specific: SpecificFromSkillSelectOptionCategoryCategory<Ref>? = nil, prerequisites: [SkillSelectOptionCategoryPrerequisite]? = nil) {
self.specific = specific
self.prerequisites = prerequisites
}
}
public struct SpecificFromSkillSelectOptionCategoryCategory<Ref: EntitySubtype>: EntitySubtype {
public let operation: SpecificFromSkillSelectOptionCategoryCategoryOperation
/// The list of specific entries.
public let list: [Ref]
public init(operation: SpecificFromSkillSelectOptionCategoryCategoryOperation, list: [Ref]) {
self.operation = operation
self.list = list
}
}
/// Only include (`Intersection`) or exclude (`Difference`) specific entries.
public enum SpecificFromSkillSelectOptionCategoryCategoryOperation: String, EntitySubtype {
case intersection = "Intersection"
case difference = "Difference"
}
public enum SkillSelectOptionCategoryPrerequisite: EntitySubtype {
case `self`(SelfPrerequisite)
case selectOption(OptionPrerequisite)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case `self` = "self"
case selectOption = "select_option"
}
private enum Discriminator: String, Decodable {
case `self` = "Self"
case selectOption = "SelectOption"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .`self`:
self = .`self`(try container.decode(SelfPrerequisite.self, forKey: .`self`))
case .selectOption:
self = .selectOption(try container.decode(OptionPrerequisite.self, forKey: .selectOption))
}
}
}
public struct SelfPrerequisite: EntitySubtype {
/// The entry requires itself on a certain Skill Rating.
public let value: Int
public init(value: Int) {
self.value = value
}
}
/// The entry requires or prohibits itself as a select option of another entry.
public struct OptionPrerequisite: EntitySubtype {
/// The target entry's identifier.
public let id: ActivatableIdentifier
/// Is the select option required (`true`) or prohibited (`false`)?
public let active: Bool
/// The required level, if any.
public let level: Int?
public init(id: ActivatableIdentifier, active: Bool, level: Int? = nil) {
self.id = id
self.active = active
self.level = level
}
}
/// Generate AP values for each entry.
public enum SelectOptionsAdventurePointsValue<Identifier: EntitySubtype>: EntitySubtype {
case derivedFromImprovementCost(SelectOptionsDeriveAdventurePointsValueFromImprovementCost)
case fixed(SelectOptionsFixedAdventurePointsValue<Identifier>)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case derivedFromImprovementCost = "derived_from_improvement_cost"
case fixed = "fixed"
}
private enum Discriminator: String, Decodable {
case derivedFromImprovementCost = "DerivedFromImprovementCost"
case fixed = "Fixed"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .derivedFromImprovementCost:
self = .derivedFromImprovementCost(try container.decode(SelectOptionsDeriveAdventurePointsValueFromImprovementCost.self, forKey: .derivedFromImprovementCost))
case .fixed:
self = .fixed(try container.decode(SelectOptionsFixedAdventurePointsValue<Identifier>.self, forKey: .fixed))
}
}
}
/// Derive the cost from the improvement cost of each entry.
///
/// **Calculation:** AP Value = Improvement Cost × `multiplier` + `offset`
public struct SelectOptionsDeriveAdventurePointsValueFromImprovementCost: EntitySubtype {
/// This number is multiplied with the improvement cost of the entry
/// (A = 1 to D = 4).
public let multiplier: Int?
/// This number is added to the multiplied improvement cost of the entry.
public let offset: Int?
public init(multiplier: Int? = nil, offset: Int? = nil) {
self.multiplier = multiplier
self.offset = offset
}
}
public struct SelectOptionsFixedAdventurePointsValue<Identifier: EntitySubtype>: EntitySubtype {
/// A mapping of skill identifiers to their specific AP values.
public let map: [SelectOptionsFixedAdventurePointsValueMapping<Identifier>]
/// The default value of an entry. Used as a fallback if no value is found in `list`.
public let `default`: Int
public init(map: [SelectOptionsFixedAdventurePointsValueMapping<Identifier>], `default`: Int) {
self.map = map
self.`default` = `default`
}
private enum CodingKeys: String, CodingKey {
case map = "map"
case `default` = "default"
}
}
public struct SelectOptionsFixedAdventurePointsValueMapping<Identifier: EntitySubtype>: EntitySubtype {
/// The entry's identifier.
public let id: Identifier
/// The AP value for the specified entry.
public let apValue: Int
public init(id: Identifier, apValue: Int) {
self.id = id
self.apValue = apValue
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case apValue = "ap_value"
}
}
public struct TargetCategoriesSelectOptionCategory: EntitySubtype {
/// A list of combat technique categories.
public let list: [SpecificTargetCategory]
public init(list: [SpecificTargetCategory]) {
self.list = list
}
}
public struct SpecificTargetCategory: EntitySubtype {
/// The target categorys identifier.
public let id: TargetCategoryIdentifier
/// The volume for this specific selection.
public let volume: Int?
public init(id: TargetCategoryIdentifier, volume: Int? = nil) {
self.id = id
self.volume = volume
}
}
@@ -0,0 +1,125 @@
//
// _ActivatableSkill.swift
// OptolithDatabaseSchema
//
public struct OneTimePerformanceParameters<CastingTime: EntitySubtype>: EntitySubtype {
public let castingTime: CastingTime
public let cost: OneTimeCost
public let range: Range
public let duration: DurationForOneTime
public init(castingTime: CastingTime, cost: OneTimeCost, range: Range, duration: DurationForOneTime) {
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
}
private enum CodingKeys: String, CodingKey {
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
}
}
public struct SustainedPerformanceParameters<CastingTime: EntitySubtype>: EntitySubtype {
public let castingTime: CastingTime
public let cost: SustainedCost
public let range: Range
public let duration: DurationForSustained?
public init(castingTime: CastingTime, cost: SustainedCost, range: Range, duration: DurationForSustained? = nil) {
self.castingTime = castingTime
self.cost = cost
self.range = range
self.duration = duration
}
private enum CodingKeys: String, CodingKey {
case castingTime = "casting_time"
case cost = "cost"
case range = "range"
case duration = "duration"
}
}
public enum FastPerformanceParameters: EntitySubtype {
case oneTime(FastOneTimePerformanceParameters)
case sustained(FastSustainedPerformanceParameters)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case oneTime = "one_time"
case sustained = "sustained"
}
private enum Discriminator: String, Decodable {
case oneTime = "OneTime"
case sustained = "Sustained"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .oneTime:
self = .oneTime(try container.decode(FastOneTimePerformanceParameters.self, forKey: .oneTime))
case .sustained:
self = .sustained(try container.decode(FastSustainedPerformanceParameters.self, forKey: .sustained))
}
}
}
public typealias FastOneTimePerformanceParameters = OneTimePerformanceParameters<FastCastingTime>
public typealias FastSustainedPerformanceParameters = SustainedPerformanceParameters<FastCastingTime>
public enum SlowPerformanceParameters: EntitySubtype {
case oneTime(SlowOneTimePerformanceParameters)
case sustained(SlowSustainedPerformanceParameters)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case oneTime = "one_time"
case sustained = "sustained"
}
private enum Discriminator: String, Decodable {
case oneTime = "OneTime"
case sustained = "Sustained"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .oneTime:
self = .oneTime(try container.decode(SlowOneTimePerformanceParameters.self, forKey: .oneTime))
case .sustained:
self = .sustained(try container.decode(SlowSustainedPerformanceParameters.self, forKey: .sustained))
}
}
}
public typealias SlowOneTimePerformanceParameters = OneTimePerformanceParameters<SlowCastingTime>
public typealias SlowSustainedPerformanceParameters = SustainedPerformanceParameters<SlowCastingTime>
public struct OldParameter: EntitySubtype {
public let full: String
public let abbr: String
public init(full: String, abbr: String) {
self.full = full
self.abbr = abbr
}
}
@@ -0,0 +1,109 @@
//
// _ActivatableSkillCastingTime.swift
// OptolithDatabaseSchema
//
public enum CastingTime<NonModifiable: EntitySubtype>: EntitySubtype {
case modifiable(ModifiableCastingTime)
case nonModifiable(NonModifiable)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case modifiable = "modifiable"
case nonModifiable = "non_modifiable"
}
private enum Discriminator: String, Decodable {
case modifiable = "Modifiable"
case nonModifiable = "NonModifiable"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .modifiable:
self = .modifiable(try container.decode(ModifiableCastingTime.self, forKey: .modifiable))
case .nonModifiable:
self = .nonModifiable(try container.decode(NonModifiable.self, forKey: .nonModifiable))
}
}
}
public struct ModifiableCastingTime: EntitySubtype {
/// The initial skill modification identifier/level.
public let initialModificationLevel: Int
public init(initialModificationLevel: Int) {
self.initialModificationLevel = initialModificationLevel
}
private enum CodingKeys: String, CodingKey {
case initialModificationLevel = "initial_modification_level"
}
}
/// The casting time may have two different values: One for use in normal time, i. e. actions, combat rounds and others, and one for use during lovemaking, which is a rule set from Aventurian Intimacy.
///
/// There must always be at least one casting time value.
public struct CastingTimeIncludingLovemaking<NonModifiable: EntitySubtype>: EntitySubtype {
/// The default casting time definition.
public let `default`: CastingTime<NonModifiable>?
/// The casting time during lovemaking. In Aventurian Intimacy, you may only use an activatable skill during lovemaking if it has a casting time used during lovemaking.
public let duringLovemaking: CastingTimeDuringLovemaking?
public init(`default`: CastingTime<NonModifiable>? = nil, duringLovemaking: CastingTimeDuringLovemaking? = nil) {
self.`default` = `default`
self.duringLovemaking = duringLovemaking
}
private enum CodingKeys: String, CodingKey {
case `default` = "default"
case duringLovemaking = "during_lovemaking"
}
}
/// The casting time during lovemaking. In Aventurian Intimacy, you may only use an activatable skill during lovemaking if it has a casting time used during lovemaking.
public struct CastingTimeDuringLovemaking: EntitySubtype {
/// The (unitless) casting time value.
public let value: Int
/// The unit of the `value`.
public let unit: CastingTimeDuringLovemakingUnit
public init(value: Int, unit: CastingTimeDuringLovemakingUnit) {
self.value = value
self.unit = unit
}
}
public enum CastingTimeDuringLovemakingUnit: String, EntitySubtype {
case seductionActions = "SeductionActions"
case rounds = "Rounds"
}
public struct FastSkillNonModifiableCastingTime: EntitySubtype {
/// The casting time value in actions.
public let actions: Int
public init(actions: Int) {
self.actions = actions
}
}
public struct SlowSkillNonModifiableCastingTime: EntitySubtype {
/// The (unitless) casting time value.
public let value: Int
public let unit: SlowSkillCastingTimeUnit
public init(value: Int, unit: SlowSkillCastingTimeUnit) {
self.value = value
self.unit = unit
}
}
public typealias FastCastingTime = CastingTimeIncludingLovemaking<FastSkillNonModifiableCastingTime>
public typealias SlowCastingTime = CastingTimeIncludingLovemaking<SlowSkillNonModifiableCastingTime>
@@ -0,0 +1,43 @@
//
// _ActivatableSkillCheckResultBased.swift
// OptolithDatabaseSchema
//
/// Defines the derived (unitless) value.
public enum CheckResultValue: String, EntitySubtype {
case qualityLevels = "QualityLevels"
case skillPoints = "SkillPoints"
}
/// Defines how the the `value` is set off against the check result.
public enum CheckResultArithmetic: String, EntitySubtype {
case multiply = "Multiply"
case divide = "Divide"
}
public struct CheckResultBasedModifier: EntitySubtype {
/// The arithmetic how to apply the `value` to the `base`.
public let arithmetic: CheckResultArithmetic
/// The value that is applied to the `base` using the defined `arithmetic`.
public let value: Int
public init(arithmetic: CheckResultArithmetic, value: Int) {
self.arithmetic = arithmetic
self.value = value
}
}
/// Defines a parameter being based on a check result.
public struct CheckResultBased: EntitySubtype {
/// The base value that is derived from the check result.
public let base: CheckResultValue
/// If defined, it modifies the base value.
public let modifier: CheckResultBasedModifier?
public init(base: CheckResultValue, modifier: CheckResultBasedModifier? = nil) {
self.base = base
self.modifier = modifier
}
}
@@ -0,0 +1,389 @@
//
// _ActivatableSkillCost.swift
// OptolithDatabaseSchema
//
public enum OneTimeCost: EntitySubtype {
case single(SingleOneTimeCost)
case conjunction(MultipleOneTimeCosts)
case disjunction(MultipleOneTimeCosts)
case map(CostMap)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case single = "single"
case conjunction = "conjunction"
case disjunction = "disjunction"
case map = "map"
}
private enum Discriminator: String, Decodable {
case single = "Single"
case conjunction = "Conjunction"
case disjunction = "Disjunction"
case map = "Map"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .single:
self = .single(try container.decode(SingleOneTimeCost.self, forKey: .single))
case .conjunction:
self = .conjunction(try container.decode(MultipleOneTimeCosts.self, forKey: .conjunction))
case .disjunction:
self = .disjunction(try container.decode(MultipleOneTimeCosts.self, forKey: .disjunction))
case .map:
self = .map(try container.decode(CostMap.self, forKey: .map))
}
}
}
public enum SingleOneTimeCost: EntitySubtype {
case modifiable(ModifiableOneTimeCost)
case nonModifiable(NonModifiableOneTimeCost)
case indefinite(IndefiniteOneTimeCost)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case modifiable = "modifiable"
case nonModifiable = "non_modifiable"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case modifiable = "Modifiable"
case nonModifiable = "NonModifiable"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .modifiable:
self = .modifiable(try container.decode(ModifiableOneTimeCost.self, forKey: .modifiable))
case .nonModifiable:
self = .nonModifiable(try container.decode(NonModifiableOneTimeCost.self, forKey: .nonModifiable))
case .indefinite:
self = .indefinite(try container.decode(IndefiniteOneTimeCost.self, forKey: .indefinite))
}
}
}
public typealias MultipleOneTimeCosts = [SingleOneTimeCost]
public struct ModifiableOneTimeCost: EntitySubtype {
/// The initial skill modification identifier/level.
public let initialModificationLevel: Int
/// The part of the cost value that has to be spent permanently.
public let permanentValue: Int?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ModifiableOneTimeCostTranslation>?
public init(initialModificationLevel: Int, permanentValue: Int? = nil, translations: LocaleMap<ModifiableOneTimeCostTranslation>? = nil) {
self.initialModificationLevel = initialModificationLevel
self.permanentValue = permanentValue
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case initialModificationLevel = "initial_modification_level"
case permanentValue = "permanent_value"
case translations = "translations"
}
}
public struct ModifiableOneTimeCostTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace
public init(replacement: ResponsiveTextReplace) {
self.replacement = replacement
}
}
public struct NonModifiableOneTimeCost: EntitySubtype {
/// If `true`, the non-modifiable value is a minimum value.
public let isMinimum: Bool?
/// The AE cost value.
public let value: Int
/// The part of the cost value that has to be spent permanently.
public let permanentValue: Int?
/// The cost have to be per a specific countable entity, e.g. `8 KP per person`.
public let per: NonModifiableOneTimeCostPerCountable?
/// All translations for the entry, identified by IETF language tag
/// (BCP47).
public let translations: LocaleMap<NonModifiableOneTimeCostTranslation>?
public init(isMinimum: Bool? = nil, value: Int, permanentValue: Int? = nil, per: NonModifiableOneTimeCostPerCountable? = nil, translations: LocaleMap<NonModifiableOneTimeCostTranslation>? = nil) {
self.isMinimum = isMinimum
self.value = value
self.permanentValue = permanentValue
self.per = per
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case isMinimum = "is_minimum"
case value = "value"
case permanentValue = "permanent_value"
case per = "per"
case translations = "translations"
}
}
public struct NonModifiableOneTimeCostPerCountable: EntitySubtype {
/// If defined, the minimum total AE that have to be spent casting the skill.
public let minimumTotal: Double?
/// All translations for the entry, identified by IETF language tag
/// (BCP47).
public let translations: LocaleMap<NonModifiableOneTimeCostPerCountableTranslation>
public init(minimumTotal: Double? = nil, translations: LocaleMap<NonModifiableOneTimeCostPerCountableTranslation>) {
self.minimumTotal = minimumTotal
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case minimumTotal = "minimum_total"
case translations = "translations"
}
}
public struct NonModifiableOneTimeCostPerCountableTranslation: EntitySubtype {
/// The countable entity name.
public let countable: ResponsiveText
public init(countable: ResponsiveText) {
self.countable = countable
}
}
public struct NonModifiableOneTimeCostTranslation: EntitySubtype {
/// A note, appended to the generated string in parenthesis.
public let note: ResponsiveTextOptional
public init(note: ResponsiveTextOptional) {
self.note = note
}
}
public struct IndefiniteOneTimeCost: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<IndefiniteOneTimeCostTranslation>
public init(translations: LocaleMap<IndefiniteOneTimeCostTranslation>) {
self.translations = translations
}
}
public struct IndefiniteOneTimeCostTranslation: EntitySubtype {
/// A description of where the cost come from.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
/// A content that is `2/4/8/16 AE for an item the size of a cup/chest/door/castle gate` may be respresented as the following map:
///
/// ```yaml
/// options:
/// - value: 2
/// label: "cup"
/// - value: 4
/// label: "chest"
/// - value: 8
/// label: "door"
/// - value: 16
/// label: "castle gate"
/// for_append: "an item the size of a"
/// ```
///
/// This will generate the exact same string as seen above given it is set for a spellwork and thus `AE` is used.
public struct CostMap: EntitySubtype {
/// The possible costs and associated labels.
public let options: [CostMapOption]
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CostMapTranslation>?
public init(options: [CostMapOption], translations: LocaleMap<CostMapTranslation>? = nil) {
self.options = options
self.translations = translations
}
}
public struct CostMapOption: EntitySubtype {
/// The full cost value for this option.
public let value: Int
/// The part of the `value` that has to be paid permanently.
public let permanentValue: Int?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CostMapOptionTranslation>?
public init(value: Int, permanentValue: Int? = nil, translations: LocaleMap<CostMapOptionTranslation>? = nil) {
self.value = value
self.permanentValue = permanentValue
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case value = "value"
case permanentValue = "permanent_value"
case translations = "translations"
}
}
public struct CostMapOptionTranslation: EntitySubtype {
/// The description of the option for cost string generation.
public let label: NonEmptyString
/// The description of the option if used standalone. Only used if different from `label`.
public let labelStandalone: NonEmptyString?
public init(label: NonEmptyString, labelStandalone: NonEmptyString? = nil) {
self.label = label
self.labelStandalone = labelStandalone
}
private enum CodingKeys: String, CodingKey {
case label = "label"
case labelStandalone = "label_standalone"
}
}
public struct CostMapTranslation: EntitySubtype {
/// Place a string between the `for` and the grouped map option labels.
public let listPrepend: NonEmptyString?
/// Place a string after the grouped map option labels.
public let listAppend: NonEmptyString?
/// 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.
public let replacement: NonEmptyString?
public init(listPrepend: NonEmptyString? = nil, listAppend: NonEmptyString? = nil, replacement: NonEmptyString? = nil) {
self.listPrepend = listPrepend
self.listAppend = listAppend
self.replacement = replacement
}
private enum CodingKeys: String, CodingKey {
case listPrepend = "list_prepend"
case listAppend = "list_append"
case replacement = "replacement"
}
}
public enum SustainedCost: EntitySubtype {
case modifiable(ModifiableSustainedCost)
case nonModifiable(NonModifiableSustainedCost)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case modifiable = "modifiable"
case nonModifiable = "non_modifiable"
}
private enum Discriminator: String, Decodable {
case modifiable = "Modifiable"
case nonModifiable = "NonModifiable"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .modifiable:
self = .modifiable(try container.decode(ModifiableSustainedCost.self, forKey: .modifiable))
case .nonModifiable:
self = .nonModifiable(try container.decode(NonModifiableSustainedCost.self, forKey: .nonModifiable))
}
}
}
public struct ModifiableSustainedCost: EntitySubtype {
/// The initial skill modification identifier/level.
public let initialModificationLevel: Int
/// The sustain interval.
public let interval: DurationUnitValue
public init(initialModificationLevel: Int, interval: DurationUnitValue) {
self.initialModificationLevel = initialModificationLevel
self.interval = interval
}
private enum CodingKeys: String, CodingKey {
case initialModificationLevel = "initial_modification_level"
case interval = "interval"
}
}
public struct NonModifiableSustainedCost: EntitySubtype {
/// If `true`, the non-modifiable value is a minimum value.
public let isMinimum: Bool?
/// The AE cost value.
public let value: Int
/// The cost have to be per a specific countable entity, e.g. `8 KP per person per 5 minutes`.
public let per: NonModifiableSustainedCostPerCountable?
/// The sustain interval.
public let interval: DurationUnitValue
public init(isMinimum: Bool? = nil, value: Int, per: NonModifiableSustainedCostPerCountable? = nil, interval: DurationUnitValue) {
self.isMinimum = isMinimum
self.value = value
self.per = per
self.interval = interval
}
private enum CodingKeys: String, CodingKey {
case isMinimum = "is_minimum"
case value = "value"
case per = "per"
case interval = "interval"
}
}
public struct NonModifiableSustainedCostPerCountable: EntitySubtype {
/// If defined, the minimum total AE that have to be spent casting the skill.
public let minimumTotal: Double?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<NonModifiableSustainedCostPerCountableTranslation>
public init(minimumTotal: Double? = nil, translations: LocaleMap<NonModifiableSustainedCostPerCountableTranslation>) {
self.minimumTotal = minimumTotal
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case minimumTotal = "minimum_total"
case translations = "translations"
}
}
public struct NonModifiableSustainedCostPerCountableTranslation: EntitySubtype {
/// The countable entity name.
public let countable: ResponsiveText
public init(countable: ResponsiveText) {
self.countable = countable
}
}
@@ -0,0 +1,219 @@
//
// _ActivatableSkillDuration.swift
// OptolithDatabaseSchema
//
public enum DurationForOneTime: EntitySubtype {
case immediate(Immediate)
case permanent(PermanentDuration)
case fixed(FixedDuration)
case checkResultBased(CheckResultBasedDuration)
case indefinite(IndefiniteDuration)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case immediate = "immediate"
case permanent = "permanent"
case fixed = "fixed"
case checkResultBased = "check_result_based"
case indefinite = "indefinite"
}
private enum Discriminator: String, Decodable {
case immediate = "Immediate"
case permanent = "Permanent"
case fixed = "Fixed"
case checkResultBased = "CheckResultBased"
case indefinite = "Indefinite"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .immediate:
self = .immediate(try container.decode(Immediate.self, forKey: .immediate))
case .permanent:
self = .permanent(try container.decode(PermanentDuration.self, forKey: .permanent))
case .fixed:
self = .fixed(try container.decode(FixedDuration.self, forKey: .fixed))
case .checkResultBased:
self = .checkResultBased(try container.decode(CheckResultBasedDuration.self, forKey: .checkResultBased))
case .indefinite:
self = .indefinite(try container.decode(IndefiniteDuration.self, forKey: .indefinite))
}
}
}
public struct Immediate: EntitySubtype {
/// Specified if the duration has a maximum time span.
public let maximum: DurationUnitValue?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ImmediateTranslation>?
public init(maximum: DurationUnitValue? = nil, translations: LocaleMap<ImmediateTranslation>? = nil) {
self.maximum = maximum
self.translations = translations
}
}
public struct ImmediateTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace
public init(replacement: ResponsiveTextReplace) {
self.replacement = replacement
}
}
public struct PermanentDuration: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<PermanentDurationTranslation>?
public init(translations: LocaleMap<PermanentDurationTranslation>? = nil) {
self.translations = translations
}
}
public struct PermanentDurationTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace
public init(replacement: ResponsiveTextReplace) {
self.replacement = replacement
}
}
public struct FixedDuration: EntitySubtype {
/// If the duration is the maximum duration, so it may end earlier.
public let isMaximum: Bool?
/// The (unitless) duration.
public let value: Int
/// The duration unit.
public let unit: DurationUnit
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<FixedDurationTranslation>?
public init(isMaximum: Bool? = nil, value: Int, unit: DurationUnit, translations: LocaleMap<FixedDurationTranslation>? = nil) {
self.isMaximum = isMaximum
self.value = value
self.unit = unit
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case value = "value"
case unit = "unit"
case translations = "translations"
}
}
public struct FixedDurationTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace
public init(replacement: ResponsiveTextReplace) {
self.replacement = replacement
}
}
/// Defines the duration being based on a check result.
public struct CheckResultBasedDuration: EntitySubtype {
/// If the duration is the maximum duration, so it may end earlier.
public let isMaximum: Bool?
/// The base value that is derived from the check result.
public let base: CheckResultValue
/// If defined, it modifies the base value.
public let modifier: CheckResultBasedModifier?
/// The duration unit.
public let unit: DurationUnit
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CheckResultBasedDurationTranslation>?
public init(isMaximum: Bool? = nil, base: CheckResultValue, modifier: CheckResultBasedModifier? = nil, unit: DurationUnit, translations: LocaleMap<CheckResultBasedDurationTranslation>? = nil) {
self.isMaximum = isMaximum
self.base = base
self.modifier = modifier
self.unit = unit
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case base = "base"
case modifier = "modifier"
case unit = "unit"
case translations = "translations"
}
}
public struct CheckResultBasedDurationTranslation: EntitySubtype {
/// A replacement string.
public let replacement: ResponsiveTextReplace
public init(replacement: ResponsiveTextReplace) {
self.replacement = replacement
}
}
public struct IndefiniteDuration: EntitySubtype {
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<IndefiniteDurationTranslation>
public init(translations: LocaleMap<IndefiniteDurationTranslation>) {
self.translations = translations
}
}
public struct IndefiniteDurationTranslation: EntitySubtype {
/// A description of the duration.
public let description: ResponsiveText
public init(description: ResponsiveText) {
self.description = description
}
}
public struct DurationForSustained: EntitySubtype {
/// The sustained skill can be active a maximum amount of time.
public let maximum: DurationUnitValue
public init(maximum: DurationUnitValue) {
self.maximum = maximum
}
}
public enum DurationUnit: String, EntitySubtype {
case seconds = "Seconds"
case minutes = "Minutes"
case hours = "Hours"
case days = "Days"
case weeks = "Weeks"
case months = "Months"
case years = "Years"
case centuries = "Centuries"
case actions = "Actions"
case combatRounds = "CombatRounds"
}
public struct DurationUnitValue: EntitySubtype {
/// The (unitless) duration value.
public let value: Int
/// The unit of the `value`.
public let unit: DurationUnit
public init(value: Int, unit: DurationUnit) {
self.value = value
self.unit = unit
}
}
@@ -0,0 +1,92 @@
//
// _ActivatableSkillEffect.swift
// OptolithDatabaseSchema
//
/// The effect description may be either a plain text or a text that is divided by a list of effects for each quality level. It may also be a list for each two quality levels.
public enum ActivatableSkillEffect: EntitySubtype {
case plain(ActivatableSkillPlainEffect)
case forEachQualityLevel(ActivatableSkillEffectForEachQualityLevel)
case forEachTwoQualityLevels(ActivatableSkillEffectForEachTwoQualityLevels)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case plain = "plain"
case forEachQualityLevel = "for_each_quality_level"
case forEachTwoQualityLevels = "for_each_two_quality_levels"
}
private enum Discriminator: String, Decodable {
case plain = "Plain"
case forEachQualityLevel = "ForEachQualityLevel"
case forEachTwoQualityLevels = "ForEachTwoQualityLevels"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .plain:
self = .plain(try container.decode(ActivatableSkillPlainEffect.self, forKey: .plain))
case .forEachQualityLevel:
self = .forEachQualityLevel(try container.decode(ActivatableSkillEffectForEachQualityLevel.self, forKey: .forEachQualityLevel))
case .forEachTwoQualityLevels:
self = .forEachTwoQualityLevels(try container.decode(ActivatableSkillEffectForEachTwoQualityLevels.self, forKey: .forEachTwoQualityLevels))
}
}
}
public struct ActivatableSkillPlainEffect: EntitySubtype {
/// The effect description.
public let text: NonEmptyMarkdown
public init(text: NonEmptyMarkdown) {
self.text = text
}
}
public struct ActivatableSkillEffectForEachQualityLevel: EntitySubtype {
/// The effect description before the list of effects for each quality level.
public let textBefore: NonEmptyMarkdown
/// The list of effects for each quality level. The first element represents QL 1, the second element QL 2, and so on.
public let qualityLevels: [NonEmptyMarkdown]
/// The effect description after the list of effects for each quality level.
public let textAfter: NonEmptyMarkdown?
public init(textBefore: NonEmptyMarkdown, qualityLevels: [NonEmptyMarkdown], textAfter: NonEmptyMarkdown? = nil) {
self.textBefore = textBefore
self.qualityLevels = qualityLevels
self.textAfter = textAfter
}
private enum CodingKeys: String, CodingKey {
case textBefore = "text_before"
case qualityLevels = "quality_levels"
case textAfter = "text_after"
}
}
public struct ActivatableSkillEffectForEachTwoQualityLevels: EntitySubtype {
/// The effect description before the list of effects for each quality level.
public let textBefore: NonEmptyMarkdown
/// The list of effects for each two quality levels. The first element represents QL 12, the second element QL 34 and the third element QL 56.
public let qualityLevels: [NonEmptyMarkdown]
/// The effect description after the list of effects for each quality level.
public let textAfter: NonEmptyMarkdown?
public init(textBefore: NonEmptyMarkdown, qualityLevels: [NonEmptyMarkdown], textAfter: NonEmptyMarkdown? = nil) {
self.textBefore = textBefore
self.qualityLevels = qualityLevels
self.textAfter = textAfter
}
private enum CodingKeys: String, CodingKey {
case textBefore = "text_before"
case qualityLevels = "quality_levels"
case textAfter = "text_after"
}
}
@@ -0,0 +1,171 @@
//
// _ActivatableSkillRange.swift
// OptolithDatabaseSchema
//
public struct Range: EntitySubtype {
public let value: RangeValue
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<RangeTranslation>?
public init(value: RangeValue, translations: LocaleMap<RangeTranslation>? = nil) {
self.value = value
self.translations = translations
}
}
public enum RangeValue: EntitySubtype {
case modifiable(ModifiableRange)
case sight
case `self`
case global
case touch
case fixed(FixedRange)
case checkResultBased(CheckResultBasedRange)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case modifiable = "modifiable"
case sight = "sight"
case `self` = "self"
case global = "global"
case touch = "touch"
case fixed = "fixed"
case checkResultBased = "check_result_based"
}
private enum Discriminator: String, Decodable {
case modifiable = "Modifiable"
case sight = "Sight"
case `self` = "Self"
case global = "Global"
case touch = "Touch"
case fixed = "Fixed"
case checkResultBased = "CheckResultBased"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .modifiable:
self = .modifiable(try container.decode(ModifiableRange.self, forKey: .modifiable))
case .sight:
self = .sight
case .`self`:
self = .`self`
case .global:
self = .global
case .touch:
self = .touch
case .fixed:
self = .fixed(try container.decode(FixedRange.self, forKey: .fixed))
case .checkResultBased:
self = .checkResultBased(try container.decode(CheckResultBasedRange.self, forKey: .checkResultBased))
}
}
}
public struct ModifiableRange: EntitySubtype {
/// If `true`, the range is a maximum range.
public let isMaximum: Bool?
/// The initial skill modification identifier/level.
public let initialModificationLevel: Int
/// If `true`, the range is a radius.
public let isRadius: Bool?
public init(isMaximum: Bool? = nil, initialModificationLevel: Int, isRadius: Bool? = nil) {
self.isMaximum = isMaximum
self.initialModificationLevel = initialModificationLevel
self.isRadius = isRadius
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case initialModificationLevel = "initial_modification_level"
case isRadius = "is_radius"
}
}
public struct FixedRange: EntitySubtype {
/// If `true`, the range is a maximum range.
public let isMaximum: Bool?
/// The (unitless) range value.
public let value: Int
/// The unit of the `value`.
public let unit: RangeUnit
/// If `true`, the range is a radius.
public let isRadius: Bool?
public init(isMaximum: Bool? = nil, value: Int, unit: RangeUnit, isRadius: Bool? = nil) {
self.isMaximum = isMaximum
self.value = value
self.unit = unit
self.isRadius = isRadius
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case value = "value"
case unit = "unit"
case isRadius = "is_radius"
}
}
/// Defines the range being based on a check result.
public struct CheckResultBasedRange: EntitySubtype {
/// If the range is the maximum range.
public let isMaximum: Bool?
/// The base value that is derived from the check result.
public let base: CheckResultValue
/// If defined, it modifies the base value.
public let modifier: CheckResultBasedModifier?
/// The duration unit.
public let unit: RangeUnit
/// If `true`, the range is a radius.
public let isRadius: Bool?
public init(isMaximum: Bool? = nil, base: CheckResultValue, modifier: CheckResultBasedModifier? = nil, unit: RangeUnit, isRadius: Bool? = nil) {
self.isMaximum = isMaximum
self.base = base
self.modifier = modifier
self.unit = unit
self.isRadius = isRadius
}
private enum CodingKeys: String, CodingKey {
case isMaximum = "is_maximum"
case base = "base"
case modifier = "modifier"
case unit = "unit"
case isRadius = "is_radius"
}
}
public struct RangeTranslation: EntitySubtype {
/// A note, appended to the generated string in parenthesis. If the generated is modified using `replacement`, the note is appended to the modifier string.
public let note: ResponsiveTextOptional?
/// A replacement string. If `note` is provided, it is appended to the replaced string.
public let replacement: ResponsiveTextReplace?
public init(note: ResponsiveTextOptional? = nil, replacement: ResponsiveTextReplace? = nil) {
self.note = note
self.replacement = replacement
}
}
public enum RangeUnit: String, EntitySubtype {
case steps = "Steps"
case miles = "Miles"
}
@@ -0,0 +1,72 @@
//
// _ActivatableSkillTargetCategory.swift
// OptolithDatabaseSchema
//
/// The target category the kind of creature or object the skill affects.
///
/// If no target categories are given, the skill applies to all target categories.
public typealias AffectedTargetCategories = [SpecificAffectedTargetCategory]
public struct SpecificAffectedTargetCategory: EntitySubtype {
public let id: SpecificAffectedTargetCategoryIdentifier
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SpecificAffectedTargetCategoryTranslation>?
public init(id: SpecificAffectedTargetCategoryIdentifier, translations: LocaleMap<SpecificAffectedTargetCategoryTranslation>? = nil) {
self.id = id
self.translations = translations
}
}
public struct SpecificAffectedTargetCategoryTranslation: EntitySubtype {
/// A note, appended to the generated string in parenthesis.
public let note: NonEmptyString
public init(note: NonEmptyString) {
self.note = note
}
}
public enum SpecificAffectedTargetCategoryIdentifier: EntitySubtype {
case `self`
case zone
case liturgicalChantsAndCeremonies
case cantrips
case predefined(TargetCategoryReference)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case `self` = "self"
case zone = "zone"
case liturgicalChantsAndCeremonies = "liturgical_chants_and_ceremonies"
case cantrips = "cantrips"
case predefined = "predefined"
}
private enum Discriminator: String, Decodable {
case `self` = "Self"
case zone = "Zone"
case liturgicalChantsAndCeremonies = "LiturgicalChantsAndCeremonies"
case cantrips = "Cantrips"
case predefined = "Predefined"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .`self`:
self = .`self`
case .zone:
self = .zone
case .liturgicalChantsAndCeremonies:
self = .liturgicalChantsAndCeremonies
case .cantrips:
self = .cantrips
case .predefined:
self = .predefined(try container.decode(TargetCategoryReference.self, forKey: .predefined))
}
}
}
@@ -0,0 +1,17 @@
//
// _AlternativeNames.swift
// OptolithDatabaseSchema
//
public struct AlternativeName: EntitySubtype {
/// An alternative name of the disease.
public let name: NonEmptyString
/// The region where this alternative name is used.
public let region: NonEmptyString?
public init(name: NonEmptyString, region: NonEmptyString? = nil) {
self.name = name
self.region = region
}
}
@@ -0,0 +1,29 @@
//
// _ArcaneTradition.swift
// OptolithDatabaseSchema
//
public struct ArcaneTradition: LocalizableEntity {
/// The arcane tradition's identifier. An unique, increasing integer.
public let id: Int
public let prerequisites: ArcaneTraditionPrerequisites
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ArcaneTraditionTranslation>
public init(id: Int, prerequisites: ArcaneTraditionPrerequisites, translations: LocaleMap<ArcaneTraditionTranslation>) {
self.id = id
self.prerequisites = prerequisites
self.translations = translations
}
}
public struct ArcaneTraditionTranslation: EntitySubtype {
/// The arcane tradition's name.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,44 @@
//
// _Blessed.swift
// OptolithDatabaseSchema
//
public enum SkillTradition: EntitySubtype {
case generalAspect(AspectReference)
case tradition(SkillTraditionWithAspects)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case generalAspect = "general_aspect"
case tradition = "tradition"
}
private enum Discriminator: String, Decodable {
case generalAspect = "GeneralAspect"
case tradition = "Tradition"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .generalAspect:
self = .generalAspect(try container.decode(AspectReference.self, forKey: .generalAspect))
case .tradition:
self = .tradition(try container.decode(SkillTraditionWithAspects.self, forKey: .tradition))
}
}
}
public struct SkillTraditionWithAspects: EntitySubtype {
/// The blessed tradition.
public let tradition: BlessedTraditionReference
/// The aspect(s) from the tradition the ceremony belongs to. Note that not all traditions have aspects. Traditions with aspects must have at least one aspect specified.
public let aspects: [AspectReference]?
public init(tradition: BlessedTraditionReference, aspects: [AspectReference]? = nil) {
self.tradition = tradition
self.aspects = aspects
}
}
@@ -0,0 +1,14 @@
//
// _CommonnessRatedAdvantageDisadvantage.swift
// OptolithDatabaseSchema
//
/// Reference to a commonness-rated advantage or disadvantage. Commonness-rating terms used in the source books are strongly recommended, common, uncommon, suggested and unsuitable.
public struct CommonnessRatedAdvantageDisadvantage<Identifier: EntitySubtype>: EntitySubtype {
/// The advantage's or disadvantage's identifier.
public let id: Identifier
public init(id: Identifier) {
self.id = id
}
}
@@ -0,0 +1,24 @@
//
// _Dice.swift
// OptolithDatabaseSchema
//
public struct Dice: EntitySubtype {
/// Number of dice of the same type. Example: 2 in 2D6.
public let number: Int
/// Number of sides on every die. Example: 6 in 2D6.
public let sides: DieType
public init(number: Int, sides: DieType) {
self.number = number
self.sides = sides
}
}
/// Number of sides on every dice. Example: 6 in 2D6.
public enum DieType: Int, EntitySubtype {
case _3 = 3
case _6 = 6
case _20 = 20
}
@@ -0,0 +1,120 @@
//
// _DiseasePoison.swift
// OptolithDatabaseSchema
//
/// Depending on the disease, apply Spirit or Toughness as a penalty to the disease roll. It may also happen that the lower of both is applied as a penalty.
public enum Resistance: String, EntitySubtype {
case spirit = "Spirit"
case toughness = "Toughness"
case lowerOfSpiritAndToughness = "LowerOfSpiritAndToughness"
}
/// What causes the disease? The GM rolls 1D20 to see if a character gets infected. If the infection check succeeds, the GM makes a disease check to determine the severity of the infection.
public struct Cause: EntitySubtype {
/// The chance to get infected by this cause, in percent.
public let chance: Int?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<CauseTranslation>
public init(chance: Int? = nil, translations: LocaleMap<CauseTranslation>) {
self.chance = chance
self.translations = translations
}
}
public struct CauseTranslation: EntitySubtype {
/// The name of the cause.
public let name: String
/// The chance to get infected by this cause. If present for this language, this overrides the universal `chance` field; they cannot be used at the same time.
public let chance: NonEmptyString?
/// An additional note about this cause.
public let note: NonEmptyString?
public init(name: String, chance: NonEmptyString? = nil, note: NonEmptyString? = nil) {
self.name = name
self.chance = chance
self.note = note
}
}
public struct DiseaseTranslation: EntitySubtype {
/// The name of the disease.
public let name: NonEmptyString
/// A list of alternative names.
public let alternativeNames: [AlternativeName]?
/// The diseases progress, in detail.
public let progress: NonEmptyMarkdown
/// After infection, how much time passes before symptoms appear?
public let incubationTime: NonEmptyString
/// The damage caused by the disease. If the disease check fails, apply the lessened effects.
public let damage: Reduceable<NonEmptyMarkdown>
/// The duration of the disease. If the disease check fails, use the lessened duration.
public let duration: Reduceable<NonEmptyMarkdown>
/// Special information about the disease.
public let special: NonEmptyMarkdown?
/// Methods known to lessen the diseases progress or relieve symptoms.
public let treatment: NonEmptyMarkdown
/// Known remedies for the disease.
public let cure: NonEmptyMarkdown
public let errata: Errata?
public init(name: NonEmptyString, alternativeNames: [AlternativeName]? = nil, progress: NonEmptyMarkdown, incubationTime: NonEmptyString, damage: Reduceable<NonEmptyMarkdown>, duration: Reduceable<NonEmptyMarkdown>, special: NonEmptyMarkdown? = nil, treatment: NonEmptyMarkdown, cure: NonEmptyMarkdown, errata: Errata? = nil) {
self.name = name
self.alternativeNames = alternativeNames
self.progress = progress
self.incubationTime = incubationTime
self.damage = damage
self.duration = duration
self.special = special
self.treatment = treatment
self.cure = cure
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case alternativeNames = "alternative_names"
case progress = "progress"
case incubationTime = "incubation_time"
case damage = "damage"
case duration = "duration"
case special = "special"
case treatment = "treatment"
case cure = "cure"
case errata = "errata"
}
}
/// An effect or other parameter that may be reduced by a failed disease check for lessening or a degraded poison.
///
/// This streamlines the wording for diseases and poison by using a unified wording for *lessened* (disease) and *degraded* (poison).
public struct Reduceable<Content: EntitySubtype>: EntitySubtype {
/// The default value. In the source, it's the text before the slash.
public let `default`: Content
/// The reduced value. In the source, it's the text after the slash. Some entries may not have a reduced value.
public let reduced: Content?
public init(`default`: Content, reduced: Content? = nil) {
self.`default` = `default`
self.reduced = reduced
}
private enum CodingKeys: String, CodingKey {
case `default` = "default"
case reduced = "reduced"
}
}
@@ -0,0 +1,60 @@
//
// _Enhancements.swift
// OptolithDatabaseSchema
//
/// A list of enhancements.
public typealias Enhancements = [Enhancement]
public struct Enhancement: EntitySubtype {
/// The enhancement's identifier. An unique, increasing integer.
public let id: Int
/// The skill rating required to learn this enhancement.
public let skillRating: Int
/// The value to multiply with the numeric representation of the associated skill's improvement cost to form the final AP cost of this enhancement.
public let adventurePointsModifier: Int
public let prerequisites: EnhancementPrerequisites?
/// Only defined if different than the associated skill.
public let src: PublicationRefs?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<EnhancementTranslation>
public init(id: Int, skillRating: Int, adventurePointsModifier: Int, prerequisites: EnhancementPrerequisites? = nil, src: PublicationRefs? = nil, translations: LocaleMap<EnhancementTranslation>) {
self.id = id
self.skillRating = skillRating
self.adventurePointsModifier = adventurePointsModifier
self.prerequisites = prerequisites
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case skillRating = "skill_rating"
case adventurePointsModifier = "adventure_points_modifier"
case prerequisites = "prerequisites"
case src = "src"
case translations = "translations"
}
}
public struct EnhancementTranslation: EntitySubtype {
/// The name of the enhancement.
public let name: String
/// The effect description.
public let effect: String
public let errata: Errata?
public init(name: String, effect: String, errata: Errata? = nil) {
self.name = name
self.effect = effect
self.errata = errata
}
}
@@ -0,0 +1,58 @@
//
// _I18n.swift
// OptolithDatabaseSchema
//
/// The item can be present in multiple pluralization categories; this object unifies handling of them. Not all of the options must be present, however, every possible category for that language should be defined. This means, that different languages may have a different amount of properties defined here. More information on pluralization and the Intl.PluralRules API on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules, https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Language_Plural_Rules and http://cldr.unicode.org/index/cldr-spec/plural-rules.
///
/// From the third link:
///
/// > The minimal pairs are those that are required for correct grammar. So
/// > because 0 and 1 don't have to form a minimal pair (it is okeven though
/// > often not optimalto say '0 people') , 0 doesn't establish a separate
/// > category. However, implementations are encouraged to provide the ability to
/// > have special plural messages for 0 in particular, so that more natural
/// > language can be used:
/// >
/// > None of your friends are online.
/// >
/// > rather than
/// >
/// > You have 0 friends online.
public struct PluralizationCategories: EntitySubtype {
public let zero: NonEmptyString?
public let one: NonEmptyString?
public let two: NonEmptyString?
public let few: NonEmptyString?
public let many: NonEmptyString?
public let other: NonEmptyString
public init(zero: NonEmptyString? = nil, one: NonEmptyString? = nil, two: NonEmptyString? = nil, few: NonEmptyString? = nil, many: NonEmptyString? = nil, other: NonEmptyString) {
self.zero = zero
self.one = one
self.two = two
self.few = few
self.many = many
self.other = other
}
}
/// The item can be different for each operating system.
public struct VaryBySystem: EntitySubtype {
public let mac: NonEmptyString
public let windows: NonEmptyString
public let linux: NonEmptyString
public init(mac: NonEmptyString, windows: NonEmptyString, linux: NonEmptyString) {
self.mac = mac
self.windows = windows
self.linux = linux
}
}
@@ -0,0 +1,855 @@
//
// _IdentifierGroup.swift
// OptolithDatabaseSchema
//
public enum ActivatableIdentifier: EntitySubtype {
case advantageIdentifier(AdvantageIdentifier)
case disadvantageIdentifier(DisadvantageIdentifier)
case advancedCombatSpecialAbilityIdentifier(AdvancedCombatSpecialAbilityIdentifier)
case advancedKarmaSpecialAbilityIdentifier(AdvancedKarmaSpecialAbilityIdentifier)
case advancedMagicalSpecialAbilityIdentifier(AdvancedMagicalSpecialAbilityIdentifier)
case advancedSkillSpecialAbilityIdentifier(AdvancedSkillSpecialAbilityIdentifier)
case ancestorGlyphIdentifier(AncestorGlyphIdentifier)
case arcaneOrbEnchantmentIdentifier(ArcaneOrbEnchantmentIdentifier)
case attireEnchantmentIdentifier(AttireEnchantmentIdentifier)
case blessedTraditionIdentifier(BlessedTraditionIdentifier)
case bowlEnchantmentIdentifier(BowlEnchantmentIdentifier)
case brawlingSpecialAbilityIdentifier(BrawlingSpecialAbilityIdentifier)
case cauldronEnchantmentIdentifier(CauldronEnchantmentIdentifier)
case ceremonialItemSpecialAbilityIdentifier(CeremonialItemSpecialAbilityIdentifier)
case chronicleEnchantmentIdentifier(ChronicleEnchantmentIdentifier)
case combatSpecialAbilityIdentifier(CombatSpecialAbilityIdentifier)
case combatStyleSpecialAbilityIdentifier(CombatStyleSpecialAbilityIdentifier)
case commandSpecialAbilityIdentifier(CommandSpecialAbilityIdentifier)
case daggerRitualIdentifier(DaggerRitualIdentifier)
case familiarSpecialAbilityIdentifier(FamiliarSpecialAbilityIdentifier)
case fatePointSexSpecialAbilityIdentifier(FatePointSexSpecialAbilityIdentifier)
case fatePointSpecialAbilityIdentifier(FatePointSpecialAbilityIdentifier)
case foolsHatEnchantmentIdentifier(FoolsHatEnchantmentIdentifier)
case generalSpecialAbilityIdentifier(GeneralSpecialAbilityIdentifier)
case instrumentEnchantmentIdentifier(InstrumentEnchantmentIdentifier)
case karmaSpecialAbilityIdentifier(KarmaSpecialAbilityIdentifier)
case krallenkettenzauberIdentifier(KrallenkettenzauberIdentifier)
case liturgicalStyleSpecialAbilityIdentifier(LiturgicalStyleSpecialAbilityIdentifier)
case lycantropicGiftIdentifier(LycantropicGiftIdentifier)
case magicalSignIdentifier(MagicalSignIdentifier)
case magicalSpecialAbilityIdentifier(MagicalSpecialAbilityIdentifier)
case magicalTraditionIdentifier(MagicalTraditionIdentifier)
case magicStyleSpecialAbilityIdentifier(MagicStyleSpecialAbilityIdentifier)
case orbEnchantmentIdentifier(OrbEnchantmentIdentifier)
case pactGiftIdentifier(PactGiftIdentifier)
case protectiveWardingCircleSpecialAbilityIdentifier(ProtectiveWardingCircleSpecialAbilityIdentifier)
case ringEnchantmentIdentifier(RingEnchantmentIdentifier)
case sermonIdentifier(SermonIdentifier)
case sexSpecialAbilityIdentifier(SexSpecialAbilityIdentifier)
case sickleRitualIdentifier(SickleRitualIdentifier)
case sikaryanDrainSpecialAbilityIdentifier(SikaryanDrainSpecialAbilityIdentifier)
case skillStyleSpecialAbilityIdentifier(SkillStyleSpecialAbilityIdentifier)
case spellSwordEnchantmentIdentifier(SpellSwordEnchantmentIdentifier)
case staffEnchantmentIdentifier(StaffEnchantmentIdentifier)
case toyEnchantmentIdentifier(ToyEnchantmentIdentifier)
case trinkhornzauberIdentifier(TrinkhornzauberIdentifier)
case vampiricGiftIdentifier(VampiricGiftIdentifier)
case visionIdentifier(VisionIdentifier)
case wandEnchantmentIdentifier(WandEnchantmentIdentifier)
case weaponEnchantmentIdentifier(WeaponEnchantmentIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let advantageIdentifier = try? container.decode(AdvantageIdentifier.self) {
self = .advantageIdentifier(advantageIdentifier)
} else if let disadvantageIdentifier = try? container.decode(DisadvantageIdentifier.self) {
self = .disadvantageIdentifier(disadvantageIdentifier)
} else if let advancedCombatSpecialAbilityIdentifier = try? container.decode(AdvancedCombatSpecialAbilityIdentifier.self) {
self = .advancedCombatSpecialAbilityIdentifier(advancedCombatSpecialAbilityIdentifier)
} else if let advancedKarmaSpecialAbilityIdentifier = try? container.decode(AdvancedKarmaSpecialAbilityIdentifier.self) {
self = .advancedKarmaSpecialAbilityIdentifier(advancedKarmaSpecialAbilityIdentifier)
} else if let advancedMagicalSpecialAbilityIdentifier = try? container.decode(AdvancedMagicalSpecialAbilityIdentifier.self) {
self = .advancedMagicalSpecialAbilityIdentifier(advancedMagicalSpecialAbilityIdentifier)
} else if let advancedSkillSpecialAbilityIdentifier = try? container.decode(AdvancedSkillSpecialAbilityIdentifier.self) {
self = .advancedSkillSpecialAbilityIdentifier(advancedSkillSpecialAbilityIdentifier)
} else if let ancestorGlyphIdentifier = try? container.decode(AncestorGlyphIdentifier.self) {
self = .ancestorGlyphIdentifier(ancestorGlyphIdentifier)
} else if let arcaneOrbEnchantmentIdentifier = try? container.decode(ArcaneOrbEnchantmentIdentifier.self) {
self = .arcaneOrbEnchantmentIdentifier(arcaneOrbEnchantmentIdentifier)
} else if let attireEnchantmentIdentifier = try? container.decode(AttireEnchantmentIdentifier.self) {
self = .attireEnchantmentIdentifier(attireEnchantmentIdentifier)
} else if let blessedTraditionIdentifier = try? container.decode(BlessedTraditionIdentifier.self) {
self = .blessedTraditionIdentifier(blessedTraditionIdentifier)
} else if let bowlEnchantmentIdentifier = try? container.decode(BowlEnchantmentIdentifier.self) {
self = .bowlEnchantmentIdentifier(bowlEnchantmentIdentifier)
} else if let brawlingSpecialAbilityIdentifier = try? container.decode(BrawlingSpecialAbilityIdentifier.self) {
self = .brawlingSpecialAbilityIdentifier(brawlingSpecialAbilityIdentifier)
} else if let cauldronEnchantmentIdentifier = try? container.decode(CauldronEnchantmentIdentifier.self) {
self = .cauldronEnchantmentIdentifier(cauldronEnchantmentIdentifier)
} else if let ceremonialItemSpecialAbilityIdentifier = try? container.decode(CeremonialItemSpecialAbilityIdentifier.self) {
self = .ceremonialItemSpecialAbilityIdentifier(ceremonialItemSpecialAbilityIdentifier)
} else if let chronicleEnchantmentIdentifier = try? container.decode(ChronicleEnchantmentIdentifier.self) {
self = .chronicleEnchantmentIdentifier(chronicleEnchantmentIdentifier)
} else if let combatSpecialAbilityIdentifier = try? container.decode(CombatSpecialAbilityIdentifier.self) {
self = .combatSpecialAbilityIdentifier(combatSpecialAbilityIdentifier)
} else if let combatStyleSpecialAbilityIdentifier = try? container.decode(CombatStyleSpecialAbilityIdentifier.self) {
self = .combatStyleSpecialAbilityIdentifier(combatStyleSpecialAbilityIdentifier)
} else if let commandSpecialAbilityIdentifier = try? container.decode(CommandSpecialAbilityIdentifier.self) {
self = .commandSpecialAbilityIdentifier(commandSpecialAbilityIdentifier)
} else if let daggerRitualIdentifier = try? container.decode(DaggerRitualIdentifier.self) {
self = .daggerRitualIdentifier(daggerRitualIdentifier)
} else if let familiarSpecialAbilityIdentifier = try? container.decode(FamiliarSpecialAbilityIdentifier.self) {
self = .familiarSpecialAbilityIdentifier(familiarSpecialAbilityIdentifier)
} else if let fatePointSexSpecialAbilityIdentifier = try? container.decode(FatePointSexSpecialAbilityIdentifier.self) {
self = .fatePointSexSpecialAbilityIdentifier(fatePointSexSpecialAbilityIdentifier)
} else if let fatePointSpecialAbilityIdentifier = try? container.decode(FatePointSpecialAbilityIdentifier.self) {
self = .fatePointSpecialAbilityIdentifier(fatePointSpecialAbilityIdentifier)
} else if let foolsHatEnchantmentIdentifier = try? container.decode(FoolsHatEnchantmentIdentifier.self) {
self = .foolsHatEnchantmentIdentifier(foolsHatEnchantmentIdentifier)
} else if let generalSpecialAbilityIdentifier = try? container.decode(GeneralSpecialAbilityIdentifier.self) {
self = .generalSpecialAbilityIdentifier(generalSpecialAbilityIdentifier)
} else if let instrumentEnchantmentIdentifier = try? container.decode(InstrumentEnchantmentIdentifier.self) {
self = .instrumentEnchantmentIdentifier(instrumentEnchantmentIdentifier)
} else if let karmaSpecialAbilityIdentifier = try? container.decode(KarmaSpecialAbilityIdentifier.self) {
self = .karmaSpecialAbilityIdentifier(karmaSpecialAbilityIdentifier)
} else if let krallenkettenzauberIdentifier = try? container.decode(KrallenkettenzauberIdentifier.self) {
self = .krallenkettenzauberIdentifier(krallenkettenzauberIdentifier)
} else if let liturgicalStyleSpecialAbilityIdentifier = try? container.decode(LiturgicalStyleSpecialAbilityIdentifier.self) {
self = .liturgicalStyleSpecialAbilityIdentifier(liturgicalStyleSpecialAbilityIdentifier)
} else if let lycantropicGiftIdentifier = try? container.decode(LycantropicGiftIdentifier.self) {
self = .lycantropicGiftIdentifier(lycantropicGiftIdentifier)
} else if let magicalSignIdentifier = try? container.decode(MagicalSignIdentifier.self) {
self = .magicalSignIdentifier(magicalSignIdentifier)
} else if let magicalSpecialAbilityIdentifier = try? container.decode(MagicalSpecialAbilityIdentifier.self) {
self = .magicalSpecialAbilityIdentifier(magicalSpecialAbilityIdentifier)
} else if let magicalTraditionIdentifier = try? container.decode(MagicalTraditionIdentifier.self) {
self = .magicalTraditionIdentifier(magicalTraditionIdentifier)
} else if let magicStyleSpecialAbilityIdentifier = try? container.decode(MagicStyleSpecialAbilityIdentifier.self) {
self = .magicStyleSpecialAbilityIdentifier(magicStyleSpecialAbilityIdentifier)
} else if let orbEnchantmentIdentifier = try? container.decode(OrbEnchantmentIdentifier.self) {
self = .orbEnchantmentIdentifier(orbEnchantmentIdentifier)
} else if let pactGiftIdentifier = try? container.decode(PactGiftIdentifier.self) {
self = .pactGiftIdentifier(pactGiftIdentifier)
} else if let protectiveWardingCircleSpecialAbilityIdentifier = try? container.decode(ProtectiveWardingCircleSpecialAbilityIdentifier.self) {
self = .protectiveWardingCircleSpecialAbilityIdentifier(protectiveWardingCircleSpecialAbilityIdentifier)
} else if let ringEnchantmentIdentifier = try? container.decode(RingEnchantmentIdentifier.self) {
self = .ringEnchantmentIdentifier(ringEnchantmentIdentifier)
} else if let sermonIdentifier = try? container.decode(SermonIdentifier.self) {
self = .sermonIdentifier(sermonIdentifier)
} else if let sexSpecialAbilityIdentifier = try? container.decode(SexSpecialAbilityIdentifier.self) {
self = .sexSpecialAbilityIdentifier(sexSpecialAbilityIdentifier)
} else if let sickleRitualIdentifier = try? container.decode(SickleRitualIdentifier.self) {
self = .sickleRitualIdentifier(sickleRitualIdentifier)
} else if let sikaryanDrainSpecialAbilityIdentifier = try? container.decode(SikaryanDrainSpecialAbilityIdentifier.self) {
self = .sikaryanDrainSpecialAbilityIdentifier(sikaryanDrainSpecialAbilityIdentifier)
} else if let skillStyleSpecialAbilityIdentifier = try? container.decode(SkillStyleSpecialAbilityIdentifier.self) {
self = .skillStyleSpecialAbilityIdentifier(skillStyleSpecialAbilityIdentifier)
} else if let spellSwordEnchantmentIdentifier = try? container.decode(SpellSwordEnchantmentIdentifier.self) {
self = .spellSwordEnchantmentIdentifier(spellSwordEnchantmentIdentifier)
} else if let staffEnchantmentIdentifier = try? container.decode(StaffEnchantmentIdentifier.self) {
self = .staffEnchantmentIdentifier(staffEnchantmentIdentifier)
} else if let toyEnchantmentIdentifier = try? container.decode(ToyEnchantmentIdentifier.self) {
self = .toyEnchantmentIdentifier(toyEnchantmentIdentifier)
} else if let trinkhornzauberIdentifier = try? container.decode(TrinkhornzauberIdentifier.self) {
self = .trinkhornzauberIdentifier(trinkhornzauberIdentifier)
} else if let vampiricGiftIdentifier = try? container.decode(VampiricGiftIdentifier.self) {
self = .vampiricGiftIdentifier(vampiricGiftIdentifier)
} else if let visionIdentifier = try? container.decode(VisionIdentifier.self) {
self = .visionIdentifier(visionIdentifier)
} else if let wandEnchantmentIdentifier = try? container.decode(WandEnchantmentIdentifier.self) {
self = .wandEnchantmentIdentifier(wandEnchantmentIdentifier)
} else if let weaponEnchantmentIdentifier = try? container.decode(WeaponEnchantmentIdentifier.self) {
self = .weaponEnchantmentIdentifier(weaponEnchantmentIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No ActivatableIdentifier type found")
}
}
}
public enum SpecialAbilityIdentifier: EntitySubtype {
case advancedCombatSpecialAbilityIdentifier(AdvancedCombatSpecialAbilityIdentifier)
case advancedKarmaSpecialAbilityIdentifier(AdvancedKarmaSpecialAbilityIdentifier)
case advancedMagicalSpecialAbilityIdentifier(AdvancedMagicalSpecialAbilityIdentifier)
case advancedSkillSpecialAbilityIdentifier(AdvancedSkillSpecialAbilityIdentifier)
case ancestorGlyphIdentifier(AncestorGlyphIdentifier)
case arcaneOrbEnchantmentIdentifier(ArcaneOrbEnchantmentIdentifier)
case attireEnchantmentIdentifier(AttireEnchantmentIdentifier)
case blessedTraditionIdentifier(BlessedTraditionIdentifier)
case bowlEnchantmentIdentifier(BowlEnchantmentIdentifier)
case brawlingSpecialAbilityIdentifier(BrawlingSpecialAbilityIdentifier)
case cauldronEnchantmentIdentifier(CauldronEnchantmentIdentifier)
case ceremonialItemSpecialAbilityIdentifier(CeremonialItemSpecialAbilityIdentifier)
case chronicleEnchantmentIdentifier(ChronicleEnchantmentIdentifier)
case combatSpecialAbilityIdentifier(CombatSpecialAbilityIdentifier)
case combatStyleSpecialAbilityIdentifier(CombatStyleSpecialAbilityIdentifier)
case commandSpecialAbilityIdentifier(CommandSpecialAbilityIdentifier)
case daggerRitualIdentifier(DaggerRitualIdentifier)
case familiarSpecialAbilityIdentifier(FamiliarSpecialAbilityIdentifier)
case fatePointSexSpecialAbilityIdentifier(FatePointSexSpecialAbilityIdentifier)
case fatePointSpecialAbilityIdentifier(FatePointSpecialAbilityIdentifier)
case foolsHatEnchantmentIdentifier(FoolsHatEnchantmentIdentifier)
case generalSpecialAbilityIdentifier(GeneralSpecialAbilityIdentifier)
case instrumentEnchantmentIdentifier(InstrumentEnchantmentIdentifier)
case karmaSpecialAbilityIdentifier(KarmaSpecialAbilityIdentifier)
case krallenkettenzauberIdentifier(KrallenkettenzauberIdentifier)
case liturgicalStyleSpecialAbilityIdentifier(LiturgicalStyleSpecialAbilityIdentifier)
case lycantropicGiftIdentifier(LycantropicGiftIdentifier)
case magicalSignIdentifier(MagicalSignIdentifier)
case magicalSpecialAbilityIdentifier(MagicalSpecialAbilityIdentifier)
case magicalTraditionIdentifier(MagicalTraditionIdentifier)
case magicStyleSpecialAbilityIdentifier(MagicStyleSpecialAbilityIdentifier)
case orbEnchantmentIdentifier(OrbEnchantmentIdentifier)
case pactGiftIdentifier(PactGiftIdentifier)
case protectiveWardingCircleSpecialAbilityIdentifier(ProtectiveWardingCircleSpecialAbilityIdentifier)
case ringEnchantmentIdentifier(RingEnchantmentIdentifier)
case sermonIdentifier(SermonIdentifier)
case sexSpecialAbilityIdentifier(SexSpecialAbilityIdentifier)
case sickleRitualIdentifier(SickleRitualIdentifier)
case sikaryanDrainSpecialAbilityIdentifier(SikaryanDrainSpecialAbilityIdentifier)
case skillStyleSpecialAbilityIdentifier(SkillStyleSpecialAbilityIdentifier)
case spellSwordEnchantmentIdentifier(SpellSwordEnchantmentIdentifier)
case staffEnchantmentIdentifier(StaffEnchantmentIdentifier)
case toyEnchantmentIdentifier(ToyEnchantmentIdentifier)
case trinkhornzauberIdentifier(TrinkhornzauberIdentifier)
case vampiricGiftIdentifier(VampiricGiftIdentifier)
case visionIdentifier(VisionIdentifier)
case wandEnchantmentIdentifier(WandEnchantmentIdentifier)
case weaponEnchantmentIdentifier(WeaponEnchantmentIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let advancedCombatSpecialAbilityIdentifier = try? container.decode(AdvancedCombatSpecialAbilityIdentifier.self) {
self = .advancedCombatSpecialAbilityIdentifier(advancedCombatSpecialAbilityIdentifier)
} else if let advancedKarmaSpecialAbilityIdentifier = try? container.decode(AdvancedKarmaSpecialAbilityIdentifier.self) {
self = .advancedKarmaSpecialAbilityIdentifier(advancedKarmaSpecialAbilityIdentifier)
} else if let advancedMagicalSpecialAbilityIdentifier = try? container.decode(AdvancedMagicalSpecialAbilityIdentifier.self) {
self = .advancedMagicalSpecialAbilityIdentifier(advancedMagicalSpecialAbilityIdentifier)
} else if let advancedSkillSpecialAbilityIdentifier = try? container.decode(AdvancedSkillSpecialAbilityIdentifier.self) {
self = .advancedSkillSpecialAbilityIdentifier(advancedSkillSpecialAbilityIdentifier)
} else if let ancestorGlyphIdentifier = try? container.decode(AncestorGlyphIdentifier.self) {
self = .ancestorGlyphIdentifier(ancestorGlyphIdentifier)
} else if let arcaneOrbEnchantmentIdentifier = try? container.decode(ArcaneOrbEnchantmentIdentifier.self) {
self = .arcaneOrbEnchantmentIdentifier(arcaneOrbEnchantmentIdentifier)
} else if let attireEnchantmentIdentifier = try? container.decode(AttireEnchantmentIdentifier.self) {
self = .attireEnchantmentIdentifier(attireEnchantmentIdentifier)
} else if let blessedTraditionIdentifier = try? container.decode(BlessedTraditionIdentifier.self) {
self = .blessedTraditionIdentifier(blessedTraditionIdentifier)
} else if let bowlEnchantmentIdentifier = try? container.decode(BowlEnchantmentIdentifier.self) {
self = .bowlEnchantmentIdentifier(bowlEnchantmentIdentifier)
} else if let brawlingSpecialAbilityIdentifier = try? container.decode(BrawlingSpecialAbilityIdentifier.self) {
self = .brawlingSpecialAbilityIdentifier(brawlingSpecialAbilityIdentifier)
} else if let cauldronEnchantmentIdentifier = try? container.decode(CauldronEnchantmentIdentifier.self) {
self = .cauldronEnchantmentIdentifier(cauldronEnchantmentIdentifier)
} else if let ceremonialItemSpecialAbilityIdentifier = try? container.decode(CeremonialItemSpecialAbilityIdentifier.self) {
self = .ceremonialItemSpecialAbilityIdentifier(ceremonialItemSpecialAbilityIdentifier)
} else if let chronicleEnchantmentIdentifier = try? container.decode(ChronicleEnchantmentIdentifier.self) {
self = .chronicleEnchantmentIdentifier(chronicleEnchantmentIdentifier)
} else if let combatSpecialAbilityIdentifier = try? container.decode(CombatSpecialAbilityIdentifier.self) {
self = .combatSpecialAbilityIdentifier(combatSpecialAbilityIdentifier)
} else if let combatStyleSpecialAbilityIdentifier = try? container.decode(CombatStyleSpecialAbilityIdentifier.self) {
self = .combatStyleSpecialAbilityIdentifier(combatStyleSpecialAbilityIdentifier)
} else if let commandSpecialAbilityIdentifier = try? container.decode(CommandSpecialAbilityIdentifier.self) {
self = .commandSpecialAbilityIdentifier(commandSpecialAbilityIdentifier)
} else if let daggerRitualIdentifier = try? container.decode(DaggerRitualIdentifier.self) {
self = .daggerRitualIdentifier(daggerRitualIdentifier)
} else if let familiarSpecialAbilityIdentifier = try? container.decode(FamiliarSpecialAbilityIdentifier.self) {
self = .familiarSpecialAbilityIdentifier(familiarSpecialAbilityIdentifier)
} else if let fatePointSexSpecialAbilityIdentifier = try? container.decode(FatePointSexSpecialAbilityIdentifier.self) {
self = .fatePointSexSpecialAbilityIdentifier(fatePointSexSpecialAbilityIdentifier)
} else if let fatePointSpecialAbilityIdentifier = try? container.decode(FatePointSpecialAbilityIdentifier.self) {
self = .fatePointSpecialAbilityIdentifier(fatePointSpecialAbilityIdentifier)
} else if let foolsHatEnchantmentIdentifier = try? container.decode(FoolsHatEnchantmentIdentifier.self) {
self = .foolsHatEnchantmentIdentifier(foolsHatEnchantmentIdentifier)
} else if let generalSpecialAbilityIdentifier = try? container.decode(GeneralSpecialAbilityIdentifier.self) {
self = .generalSpecialAbilityIdentifier(generalSpecialAbilityIdentifier)
} else if let instrumentEnchantmentIdentifier = try? container.decode(InstrumentEnchantmentIdentifier.self) {
self = .instrumentEnchantmentIdentifier(instrumentEnchantmentIdentifier)
} else if let karmaSpecialAbilityIdentifier = try? container.decode(KarmaSpecialAbilityIdentifier.self) {
self = .karmaSpecialAbilityIdentifier(karmaSpecialAbilityIdentifier)
} else if let krallenkettenzauberIdentifier = try? container.decode(KrallenkettenzauberIdentifier.self) {
self = .krallenkettenzauberIdentifier(krallenkettenzauberIdentifier)
} else if let liturgicalStyleSpecialAbilityIdentifier = try? container.decode(LiturgicalStyleSpecialAbilityIdentifier.self) {
self = .liturgicalStyleSpecialAbilityIdentifier(liturgicalStyleSpecialAbilityIdentifier)
} else if let lycantropicGiftIdentifier = try? container.decode(LycantropicGiftIdentifier.self) {
self = .lycantropicGiftIdentifier(lycantropicGiftIdentifier)
} else if let magicalSignIdentifier = try? container.decode(MagicalSignIdentifier.self) {
self = .magicalSignIdentifier(magicalSignIdentifier)
} else if let magicalSpecialAbilityIdentifier = try? container.decode(MagicalSpecialAbilityIdentifier.self) {
self = .magicalSpecialAbilityIdentifier(magicalSpecialAbilityIdentifier)
} else if let magicalTraditionIdentifier = try? container.decode(MagicalTraditionIdentifier.self) {
self = .magicalTraditionIdentifier(magicalTraditionIdentifier)
} else if let magicStyleSpecialAbilityIdentifier = try? container.decode(MagicStyleSpecialAbilityIdentifier.self) {
self = .magicStyleSpecialAbilityIdentifier(magicStyleSpecialAbilityIdentifier)
} else if let orbEnchantmentIdentifier = try? container.decode(OrbEnchantmentIdentifier.self) {
self = .orbEnchantmentIdentifier(orbEnchantmentIdentifier)
} else if let pactGiftIdentifier = try? container.decode(PactGiftIdentifier.self) {
self = .pactGiftIdentifier(pactGiftIdentifier)
} else if let protectiveWardingCircleSpecialAbilityIdentifier = try? container.decode(ProtectiveWardingCircleSpecialAbilityIdentifier.self) {
self = .protectiveWardingCircleSpecialAbilityIdentifier(protectiveWardingCircleSpecialAbilityIdentifier)
} else if let ringEnchantmentIdentifier = try? container.decode(RingEnchantmentIdentifier.self) {
self = .ringEnchantmentIdentifier(ringEnchantmentIdentifier)
} else if let sermonIdentifier = try? container.decode(SermonIdentifier.self) {
self = .sermonIdentifier(sermonIdentifier)
} else if let sexSpecialAbilityIdentifier = try? container.decode(SexSpecialAbilityIdentifier.self) {
self = .sexSpecialAbilityIdentifier(sexSpecialAbilityIdentifier)
} else if let sickleRitualIdentifier = try? container.decode(SickleRitualIdentifier.self) {
self = .sickleRitualIdentifier(sickleRitualIdentifier)
} else if let sikaryanDrainSpecialAbilityIdentifier = try? container.decode(SikaryanDrainSpecialAbilityIdentifier.self) {
self = .sikaryanDrainSpecialAbilityIdentifier(sikaryanDrainSpecialAbilityIdentifier)
} else if let skillStyleSpecialAbilityIdentifier = try? container.decode(SkillStyleSpecialAbilityIdentifier.self) {
self = .skillStyleSpecialAbilityIdentifier(skillStyleSpecialAbilityIdentifier)
} else if let spellSwordEnchantmentIdentifier = try? container.decode(SpellSwordEnchantmentIdentifier.self) {
self = .spellSwordEnchantmentIdentifier(spellSwordEnchantmentIdentifier)
} else if let staffEnchantmentIdentifier = try? container.decode(StaffEnchantmentIdentifier.self) {
self = .staffEnchantmentIdentifier(staffEnchantmentIdentifier)
} else if let toyEnchantmentIdentifier = try? container.decode(ToyEnchantmentIdentifier.self) {
self = .toyEnchantmentIdentifier(toyEnchantmentIdentifier)
} else if let trinkhornzauberIdentifier = try? container.decode(TrinkhornzauberIdentifier.self) {
self = .trinkhornzauberIdentifier(trinkhornzauberIdentifier)
} else if let vampiricGiftIdentifier = try? container.decode(VampiricGiftIdentifier.self) {
self = .vampiricGiftIdentifier(vampiricGiftIdentifier)
} else if let visionIdentifier = try? container.decode(VisionIdentifier.self) {
self = .visionIdentifier(visionIdentifier)
} else if let wandEnchantmentIdentifier = try? container.decode(WandEnchantmentIdentifier.self) {
self = .wandEnchantmentIdentifier(wandEnchantmentIdentifier)
} else if let weaponEnchantmentIdentifier = try? container.decode(WeaponEnchantmentIdentifier.self) {
self = .weaponEnchantmentIdentifier(weaponEnchantmentIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No SpecialAbilityIdentifier type found")
}
}
}
public enum CombatRelatedSpecialAbilityIdentifier: EntitySubtype {
case combatSpecialAbilityIdentifier(CombatSpecialAbilityIdentifier)
case combatStyleSpecialAbilityIdentifier(CombatStyleSpecialAbilityIdentifier)
case advancedCombatSpecialAbilityIdentifier(AdvancedCombatSpecialAbilityIdentifier)
case commandSpecialAbilityIdentifier(CommandSpecialAbilityIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let combatSpecialAbilityIdentifier = try? container.decode(CombatSpecialAbilityIdentifier.self) {
self = .combatSpecialAbilityIdentifier(combatSpecialAbilityIdentifier)
} else if let combatStyleSpecialAbilityIdentifier = try? container.decode(CombatStyleSpecialAbilityIdentifier.self) {
self = .combatStyleSpecialAbilityIdentifier(combatStyleSpecialAbilityIdentifier)
} else if let advancedCombatSpecialAbilityIdentifier = try? container.decode(AdvancedCombatSpecialAbilityIdentifier.self) {
self = .advancedCombatSpecialAbilityIdentifier(advancedCombatSpecialAbilityIdentifier)
} else if let commandSpecialAbilityIdentifier = try? container.decode(CommandSpecialAbilityIdentifier.self) {
self = .commandSpecialAbilityIdentifier(commandSpecialAbilityIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No CombatRelatedSpecialAbilityIdentifier type found")
}
}
}
public enum TraditionIdentifier: EntitySubtype {
case magicalTraditionIdentifier(MagicalTraditionIdentifier)
case blessedTraditionIdentifier(BlessedTraditionIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let magicalTraditionIdentifier = try? container.decode(MagicalTraditionIdentifier.self) {
self = .magicalTraditionIdentifier(magicalTraditionIdentifier)
} else if let blessedTraditionIdentifier = try? container.decode(BlessedTraditionIdentifier.self) {
self = .blessedTraditionIdentifier(blessedTraditionIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No TraditionIdentifier type found")
}
}
}
public enum RatedIdentifier: EntitySubtype {
case attributeIdentifier(AttributeIdentifier)
case skillIdentifier(SkillIdentifier)
case closeCombatTechniqueIdentifier(CloseCombatTechniqueIdentifier)
case rangedCombatTechniqueIdentifier(RangedCombatTechniqueIdentifier)
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let attributeIdentifier = try? container.decode(AttributeIdentifier.self) {
self = .attributeIdentifier(attributeIdentifier)
} else if let skillIdentifier = try? container.decode(SkillIdentifier.self) {
self = .skillIdentifier(skillIdentifier)
} else if let closeCombatTechniqueIdentifier = try? container.decode(CloseCombatTechniqueIdentifier.self) {
self = .closeCombatTechniqueIdentifier(closeCombatTechniqueIdentifier)
} else if let rangedCombatTechniqueIdentifier = try? container.decode(RangedCombatTechniqueIdentifier.self) {
self = .rangedCombatTechniqueIdentifier(rangedCombatTechniqueIdentifier)
} else if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No RatedIdentifier type found")
}
}
}
public enum SkillishIdentifier: EntitySubtype {
case skillIdentifier(SkillIdentifier)
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let skillIdentifier = try? container.decode(SkillIdentifier.self) {
self = .skillIdentifier(skillIdentifier)
} else if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No SkillishIdentifier type found")
}
}
}
public enum AdvancedSpecialAbilityRestrictedOptionIdentifier: EntitySubtype {
case generalIdentifier(GeneralIdentifier)
case skillIdentifier(SkillIdentifier)
case elementIdentifier(ElementIdentifier)
case aspectIdentifier(AspectIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let generalIdentifier = try? container.decode(GeneralIdentifier.self) {
self = .generalIdentifier(generalIdentifier)
} else if let skillIdentifier = try? container.decode(SkillIdentifier.self) {
self = .skillIdentifier(skillIdentifier)
} else if let elementIdentifier = try? container.decode(ElementIdentifier.self) {
self = .elementIdentifier(elementIdentifier)
} else if let aspectIdentifier = try? container.decode(AspectIdentifier.self) {
self = .aspectIdentifier(aspectIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No AdvancedSpecialAbilityRestrictedOptionIdentifier type found")
}
}
}
public enum VolumePointsOptionReferenceIdentifier: EntitySubtype {
case generalIdentifier(GeneralIdentifier)
case animalShapeSizeIdentifier(AnimalShapeSizeIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let generalIdentifier = try? container.decode(GeneralIdentifier.self) {
self = .generalIdentifier(generalIdentifier)
} else if let animalShapeSizeIdentifier = try? container.decode(AnimalShapeSizeIdentifier.self) {
self = .animalShapeSizeIdentifier(animalShapeSizeIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No VolumePointsOptionReferenceIdentifier type found")
}
}
}
public enum CombatTechniqueIdentifier: EntitySubtype {
case closeCombatTechniqueIdentifier(CloseCombatTechniqueIdentifier)
case rangedCombatTechniqueIdentifier(RangedCombatTechniqueIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let closeCombatTechniqueIdentifier = try? container.decode(CloseCombatTechniqueIdentifier.self) {
self = .closeCombatTechniqueIdentifier(closeCombatTechniqueIdentifier)
} else if let rangedCombatTechniqueIdentifier = try? container.decode(RangedCombatTechniqueIdentifier.self) {
self = .rangedCombatTechniqueIdentifier(rangedCombatTechniqueIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No CombatTechniqueIdentifier type found")
}
}
}
public enum ExtensionRuleIdentifier: EntitySubtype {
case focusRuleIdentifier(FocusRuleIdentifier)
case optionalRuleIdentifier(OptionalRuleIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let focusRuleIdentifier = try? container.decode(FocusRuleIdentifier.self) {
self = .focusRuleIdentifier(focusRuleIdentifier)
} else if let optionalRuleIdentifier = try? container.decode(OptionalRuleIdentifier.self) {
self = .optionalRuleIdentifier(optionalRuleIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No ExtensionRuleIdentifier type found")
}
}
}
public enum SkillWithEnhancementsIdentifier: EntitySubtype {
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No SkillWithEnhancementsIdentifier type found")
}
}
}
public enum SpellworkIdentifier: EntitySubtype {
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No SpellworkIdentifier type found")
}
}
}
public enum LiturgyIdentifier: EntitySubtype {
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No LiturgyIdentifier type found")
}
}
}
public enum MagicalActionIdentifier: EntitySubtype {
case curseIdentifier(CurseIdentifier)
case elvenMagicalSongIdentifier(ElvenMagicalSongIdentifier)
case dominationRitualIdentifier(DominationRitualIdentifier)
case magicalMelodyIdentifier(MagicalMelodyIdentifier)
case magicalDanceIdentifier(MagicalDanceIdentifier)
case jesterTrickIdentifier(JesterTrickIdentifier)
case animistPowerIdentifier(AnimistPowerIdentifier)
case geodeRitualIdentifier(GeodeRitualIdentifier)
case zibiljaRitualIdentifier(ZibiljaRitualIdentifier)
case magicalRuneIdentifier(MagicalRuneIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let curseIdentifier = try? container.decode(CurseIdentifier.self) {
self = .curseIdentifier(curseIdentifier)
} else if let elvenMagicalSongIdentifier = try? container.decode(ElvenMagicalSongIdentifier.self) {
self = .elvenMagicalSongIdentifier(elvenMagicalSongIdentifier)
} else if let dominationRitualIdentifier = try? container.decode(DominationRitualIdentifier.self) {
self = .dominationRitualIdentifier(dominationRitualIdentifier)
} else if let magicalMelodyIdentifier = try? container.decode(MagicalMelodyIdentifier.self) {
self = .magicalMelodyIdentifier(magicalMelodyIdentifier)
} else if let magicalDanceIdentifier = try? container.decode(MagicalDanceIdentifier.self) {
self = .magicalDanceIdentifier(magicalDanceIdentifier)
} else if let jesterTrickIdentifier = try? container.decode(JesterTrickIdentifier.self) {
self = .jesterTrickIdentifier(jesterTrickIdentifier)
} else if let animistPowerIdentifier = try? container.decode(AnimistPowerIdentifier.self) {
self = .animistPowerIdentifier(animistPowerIdentifier)
} else if let geodeRitualIdentifier = try? container.decode(GeodeRitualIdentifier.self) {
self = .geodeRitualIdentifier(geodeRitualIdentifier)
} else if let zibiljaRitualIdentifier = try? container.decode(ZibiljaRitualIdentifier.self) {
self = .zibiljaRitualIdentifier(zibiljaRitualIdentifier)
} else if let magicalRuneIdentifier = try? container.decode(MagicalRuneIdentifier.self) {
self = .magicalRuneIdentifier(magicalRuneIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No MagicalActionIdentifier type found")
}
}
}
public enum SelectOptionIdentifier: EntitySubtype {
case generalIdentifier(GeneralIdentifier)
case blessingIdentifier(BlessingIdentifier)
case cantripIdentifier(CantripIdentifier)
case tradeSecretIdentifier(TradeSecretIdentifier)
case scriptIdentifier(ScriptIdentifier)
case animalShapeIdentifier(AnimalShapeIdentifier)
case arcaneBardTraditionIdentifier(ArcaneBardTraditionIdentifier)
case arcaneDancerTraditionIdentifier(ArcaneDancerTraditionIdentifier)
case sexPracticeIdentifier(SexPracticeIdentifier)
case raceIdentifier(RaceIdentifier)
case cultureIdentifier(CultureIdentifier)
case blessedTraditionIdentifier(BlessedTraditionIdentifier)
case elementIdentifier(ElementIdentifier)
case propertyIdentifier(PropertyIdentifier)
case aspectIdentifier(AspectIdentifier)
case diseaseIdentifier(DiseaseIdentifier)
case poisonIdentifier(PoisonIdentifier)
case languageIdentifier(LanguageIdentifier)
case skillIdentifier(SkillIdentifier)
case closeCombatTechniqueIdentifier(CloseCombatTechniqueIdentifier)
case rangedCombatTechniqueIdentifier(RangedCombatTechniqueIdentifier)
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
case targetCategoryIdentifier(TargetCategoryIdentifier)
case patronIdentifier(PatronIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let generalIdentifier = try? container.decode(GeneralIdentifier.self) {
self = .generalIdentifier(generalIdentifier)
} else if let blessingIdentifier = try? container.decode(BlessingIdentifier.self) {
self = .blessingIdentifier(blessingIdentifier)
} else if let cantripIdentifier = try? container.decode(CantripIdentifier.self) {
self = .cantripIdentifier(cantripIdentifier)
} else if let tradeSecretIdentifier = try? container.decode(TradeSecretIdentifier.self) {
self = .tradeSecretIdentifier(tradeSecretIdentifier)
} else if let scriptIdentifier = try? container.decode(ScriptIdentifier.self) {
self = .scriptIdentifier(scriptIdentifier)
} else if let animalShapeIdentifier = try? container.decode(AnimalShapeIdentifier.self) {
self = .animalShapeIdentifier(animalShapeIdentifier)
} else if let arcaneBardTraditionIdentifier = try? container.decode(ArcaneBardTraditionIdentifier.self) {
self = .arcaneBardTraditionIdentifier(arcaneBardTraditionIdentifier)
} else if let arcaneDancerTraditionIdentifier = try? container.decode(ArcaneDancerTraditionIdentifier.self) {
self = .arcaneDancerTraditionIdentifier(arcaneDancerTraditionIdentifier)
} else if let sexPracticeIdentifier = try? container.decode(SexPracticeIdentifier.self) {
self = .sexPracticeIdentifier(sexPracticeIdentifier)
} else if let raceIdentifier = try? container.decode(RaceIdentifier.self) {
self = .raceIdentifier(raceIdentifier)
} else if let cultureIdentifier = try? container.decode(CultureIdentifier.self) {
self = .cultureIdentifier(cultureIdentifier)
} else if let blessedTraditionIdentifier = try? container.decode(BlessedTraditionIdentifier.self) {
self = .blessedTraditionIdentifier(blessedTraditionIdentifier)
} else if let elementIdentifier = try? container.decode(ElementIdentifier.self) {
self = .elementIdentifier(elementIdentifier)
} else if let propertyIdentifier = try? container.decode(PropertyIdentifier.self) {
self = .propertyIdentifier(propertyIdentifier)
} else if let aspectIdentifier = try? container.decode(AspectIdentifier.self) {
self = .aspectIdentifier(aspectIdentifier)
} else if let diseaseIdentifier = try? container.decode(DiseaseIdentifier.self) {
self = .diseaseIdentifier(diseaseIdentifier)
} else if let poisonIdentifier = try? container.decode(PoisonIdentifier.self) {
self = .poisonIdentifier(poisonIdentifier)
} else if let languageIdentifier = try? container.decode(LanguageIdentifier.self) {
self = .languageIdentifier(languageIdentifier)
} else if let skillIdentifier = try? container.decode(SkillIdentifier.self) {
self = .skillIdentifier(skillIdentifier)
} else if let closeCombatTechniqueIdentifier = try? container.decode(CloseCombatTechniqueIdentifier.self) {
self = .closeCombatTechniqueIdentifier(closeCombatTechniqueIdentifier)
} else if let rangedCombatTechniqueIdentifier = try? container.decode(RangedCombatTechniqueIdentifier.self) {
self = .rangedCombatTechniqueIdentifier(rangedCombatTechniqueIdentifier)
} else if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else if let targetCategoryIdentifier = try? container.decode(TargetCategoryIdentifier.self) {
self = .targetCategoryIdentifier(targetCategoryIdentifier)
} else if let patronIdentifier = try? container.decode(PatronIdentifier.self) {
self = .patronIdentifier(patronIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No SelectOptionIdentifier type found")
}
}
}
public enum RequirableSelectOptionIdentifier: EntitySubtype {
case generalIdentifier(GeneralIdentifier)
case skillIdentifier(SkillIdentifier)
case closeCombatTechniqueIdentifier(CloseCombatTechniqueIdentifier)
case rangedCombatTechniqueIdentifier(RangedCombatTechniqueIdentifier)
case propertyIdentifier(PropertyIdentifier)
case aspectIdentifier(AspectIdentifier)
case languageIdentifier(LanguageIdentifier)
case animalShapeIdentifier(AnimalShapeIdentifier)
case liturgicalChantIdentifier(LiturgicalChantIdentifier)
case ceremonyIdentifier(CeremonyIdentifier)
case spellIdentifier(SpellIdentifier)
case ritualIdentifier(RitualIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let generalIdentifier = try? container.decode(GeneralIdentifier.self) {
self = .generalIdentifier(generalIdentifier)
} else if let skillIdentifier = try? container.decode(SkillIdentifier.self) {
self = .skillIdentifier(skillIdentifier)
} else if let closeCombatTechniqueIdentifier = try? container.decode(CloseCombatTechniqueIdentifier.self) {
self = .closeCombatTechniqueIdentifier(closeCombatTechniqueIdentifier)
} else if let rangedCombatTechniqueIdentifier = try? container.decode(RangedCombatTechniqueIdentifier.self) {
self = .rangedCombatTechniqueIdentifier(rangedCombatTechniqueIdentifier)
} else if let propertyIdentifier = try? container.decode(PropertyIdentifier.self) {
self = .propertyIdentifier(propertyIdentifier)
} else if let aspectIdentifier = try? container.decode(AspectIdentifier.self) {
self = .aspectIdentifier(aspectIdentifier)
} else if let languageIdentifier = try? container.decode(LanguageIdentifier.self) {
self = .languageIdentifier(languageIdentifier)
} else if let animalShapeIdentifier = try? container.decode(AnimalShapeIdentifier.self) {
self = .animalShapeIdentifier(animalShapeIdentifier)
} else if let liturgicalChantIdentifier = try? container.decode(LiturgicalChantIdentifier.self) {
self = .liturgicalChantIdentifier(liturgicalChantIdentifier)
} else if let ceremonyIdentifier = try? container.decode(CeremonyIdentifier.self) {
self = .ceremonyIdentifier(ceremonyIdentifier)
} else if let spellIdentifier = try? container.decode(SpellIdentifier.self) {
self = .spellIdentifier(spellIdentifier)
} else if let ritualIdentifier = try? container.decode(RitualIdentifier.self) {
self = .ritualIdentifier(ritualIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No RequirableSelectOptionIdentifier type found")
}
}
}
public enum CoreRuleDerivableContentIdentifier: EntitySubtype {
case magicalTraditionIdentifier(MagicalTraditionIdentifier)
case familiarSpecialAbilityIdentifier(FamiliarSpecialAbilityIdentifier)
case magicalSpecialAbilityIdentifier(MagicalSpecialAbilityIdentifier)
case blessedTraditionIdentifier(BlessedTraditionIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let magicalTraditionIdentifier = try? container.decode(MagicalTraditionIdentifier.self) {
self = .magicalTraditionIdentifier(magicalTraditionIdentifier)
} else if let familiarSpecialAbilityIdentifier = try? container.decode(FamiliarSpecialAbilityIdentifier.self) {
self = .familiarSpecialAbilityIdentifier(familiarSpecialAbilityIdentifier)
} else if let magicalSpecialAbilityIdentifier = try? container.decode(MagicalSpecialAbilityIdentifier.self) {
self = .magicalSpecialAbilityIdentifier(magicalSpecialAbilityIdentifier)
} else if let blessedTraditionIdentifier = try? container.decode(BlessedTraditionIdentifier.self) {
self = .blessedTraditionIdentifier(blessedTraditionIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No CoreRuleDerivableContentIdentifier type found")
}
}
}
public enum EquipmentIdentifier: EntitySubtype {
case ammunitionIdentifier(AmmunitionIdentifier)
case animalIdentifier(AnimalIdentifier)
case animalCareIdentifier(AnimalCareIdentifier)
case armorIdentifier(ArmorIdentifier)
case bandageOrRemedyIdentifier(BandageOrRemedyIdentifier)
case bookIdentifier(BookIdentifier)
case ceremonialItemIdentifier(CeremonialItemIdentifier)
case clothesIdentifier(ClothesIdentifier)
case containerIdentifier(ContainerIdentifier)
case elixirIdentifier(ElixirIdentifier)
case equipmentOfBlessedOnesIdentifier(EquipmentOfBlessedOnesIdentifier)
case gemOrPreciousStoneIdentifier(GemOrPreciousStoneIdentifier)
case illuminationLightSourceIdentifier(IlluminationLightSourceIdentifier)
case illuminationRefillsOrSuppliesIdentifier(IlluminationRefillsOrSuppliesIdentifier)
case jewelryIdentifier(JewelryIdentifier)
case liebesspielzeugIdentifier(LiebesspielzeugIdentifier)
case luxuryGoodIdentifier(LuxuryGoodIdentifier)
case magicalArtifactIdentifier(MagicalArtifactIdentifier)
case musicalInstrumentIdentifier(MusicalInstrumentIdentifier)
case orienteeringAidIdentifier(OrienteeringAidIdentifier)
case poisonIdentifier(PoisonIdentifier)
case ropeOrChainIdentifier(RopeOrChainIdentifier)
case stationaryIdentifier(StationaryIdentifier)
case thievesToolIdentifier(ThievesToolIdentifier)
case toolOfTheTradeIdentifier(ToolOfTheTradeIdentifier)
case travelGearOrToolIdentifier(TravelGearOrToolIdentifier)
case vehicleIdentifier(VehicleIdentifier)
case weaponIdentifier(WeaponIdentifier)
case weaponAccessoryIdentifier(WeaponAccessoryIdentifier)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let ammunitionIdentifier = try? container.decode(AmmunitionIdentifier.self) {
self = .ammunitionIdentifier(ammunitionIdentifier)
} else if let animalIdentifier = try? container.decode(AnimalIdentifier.self) {
self = .animalIdentifier(animalIdentifier)
} else if let animalCareIdentifier = try? container.decode(AnimalCareIdentifier.self) {
self = .animalCareIdentifier(animalCareIdentifier)
} else if let armorIdentifier = try? container.decode(ArmorIdentifier.self) {
self = .armorIdentifier(armorIdentifier)
} else if let bandageOrRemedyIdentifier = try? container.decode(BandageOrRemedyIdentifier.self) {
self = .bandageOrRemedyIdentifier(bandageOrRemedyIdentifier)
} else if let bookIdentifier = try? container.decode(BookIdentifier.self) {
self = .bookIdentifier(bookIdentifier)
} else if let ceremonialItemIdentifier = try? container.decode(CeremonialItemIdentifier.self) {
self = .ceremonialItemIdentifier(ceremonialItemIdentifier)
} else if let clothesIdentifier = try? container.decode(ClothesIdentifier.self) {
self = .clothesIdentifier(clothesIdentifier)
} else if let containerIdentifier = try? container.decode(ContainerIdentifier.self) {
self = .containerIdentifier(containerIdentifier)
} else if let elixirIdentifier = try? container.decode(ElixirIdentifier.self) {
self = .elixirIdentifier(elixirIdentifier)
} else if let equipmentOfBlessedOnesIdentifier = try? container.decode(EquipmentOfBlessedOnesIdentifier.self) {
self = .equipmentOfBlessedOnesIdentifier(equipmentOfBlessedOnesIdentifier)
} else if let gemOrPreciousStoneIdentifier = try? container.decode(GemOrPreciousStoneIdentifier.self) {
self = .gemOrPreciousStoneIdentifier(gemOrPreciousStoneIdentifier)
} else if let illuminationLightSourceIdentifier = try? container.decode(IlluminationLightSourceIdentifier.self) {
self = .illuminationLightSourceIdentifier(illuminationLightSourceIdentifier)
} else if let illuminationRefillsOrSuppliesIdentifier = try? container.decode(IlluminationRefillsOrSuppliesIdentifier.self) {
self = .illuminationRefillsOrSuppliesIdentifier(illuminationRefillsOrSuppliesIdentifier)
} else if let jewelryIdentifier = try? container.decode(JewelryIdentifier.self) {
self = .jewelryIdentifier(jewelryIdentifier)
} else if let liebesspielzeugIdentifier = try? container.decode(LiebesspielzeugIdentifier.self) {
self = .liebesspielzeugIdentifier(liebesspielzeugIdentifier)
} else if let luxuryGoodIdentifier = try? container.decode(LuxuryGoodIdentifier.self) {
self = .luxuryGoodIdentifier(luxuryGoodIdentifier)
} else if let magicalArtifactIdentifier = try? container.decode(MagicalArtifactIdentifier.self) {
self = .magicalArtifactIdentifier(magicalArtifactIdentifier)
} else if let musicalInstrumentIdentifier = try? container.decode(MusicalInstrumentIdentifier.self) {
self = .musicalInstrumentIdentifier(musicalInstrumentIdentifier)
} else if let orienteeringAidIdentifier = try? container.decode(OrienteeringAidIdentifier.self) {
self = .orienteeringAidIdentifier(orienteeringAidIdentifier)
} else if let poisonIdentifier = try? container.decode(PoisonIdentifier.self) {
self = .poisonIdentifier(poisonIdentifier)
} else if let ropeOrChainIdentifier = try? container.decode(RopeOrChainIdentifier.self) {
self = .ropeOrChainIdentifier(ropeOrChainIdentifier)
} else if let stationaryIdentifier = try? container.decode(StationaryIdentifier.self) {
self = .stationaryIdentifier(stationaryIdentifier)
} else if let thievesToolIdentifier = try? container.decode(ThievesToolIdentifier.self) {
self = .thievesToolIdentifier(thievesToolIdentifier)
} else if let toolOfTheTradeIdentifier = try? container.decode(ToolOfTheTradeIdentifier.self) {
self = .toolOfTheTradeIdentifier(toolOfTheTradeIdentifier)
} else if let travelGearOrToolIdentifier = try? container.decode(TravelGearOrToolIdentifier.self) {
self = .travelGearOrToolIdentifier(travelGearOrToolIdentifier)
} else if let vehicleIdentifier = try? container.decode(VehicleIdentifier.self) {
self = .vehicleIdentifier(vehicleIdentifier)
} else if let weaponIdentifier = try? container.decode(WeaponIdentifier.self) {
self = .weaponIdentifier(weaponIdentifier)
} else if let weaponAccessoryIdentifier = try? container.decode(WeaponAccessoryIdentifier.self) {
self = .weaponAccessoryIdentifier(weaponAccessoryIdentifier)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No EquipmentIdentifier type found")
}
}
}
public enum OneOrManyNumericIdentifiers: EntitySubtype {
case oneNumericIdentifier(OneNumericIdentifier)
case manyNumericIdentifiers(ManyNumericIdentifiers)
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
if let oneNumericIdentifier = try? container.decode(OneNumericIdentifier.self) {
self = .oneNumericIdentifier(oneNumericIdentifier)
} else if let manyNumericIdentifiers = try? container.decode(ManyNumericIdentifiers.self) {
self = .manyNumericIdentifiers(manyNumericIdentifiers)
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "No OneOrManyNumericIdentifiers type found")
}
}
}
public typealias OneNumericIdentifier = Double
public typealias ManyNumericIdentifiers = [OneNumericIdentifier]
@@ -0,0 +1,11 @@
//
// _ImprovementCost.swift
// OptolithDatabaseSchema
//
public enum ImprovementCost: String, EntitySubtype {
case a = "A"
case b = "B"
case c = "C"
case d = "D"
}
@@ -0,0 +1,52 @@
//
// _Influence.swift
// OptolithDatabaseSchema
//
public struct Influence: LocalizableEntity {
/// The influence's identifier. An unique, increasing integer.
public let id: Int
public let prerequisites: InfluencePrerequisites
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<InfluenceTranslation>
public init(id: Int, prerequisites: InfluencePrerequisites, src: PublicationRefs, translations: LocaleMap<InfluenceTranslation>) {
self.id = id
self.prerequisites = prerequisites
self.src = src
self.translations = translations
}
}
public struct InfluenceTranslation: EntitySubtype {
/// The influence name.
public let name: NonEmptyString
/// The effects of the influence. They should be sorted like they are in the book.
public let effects: [InfluenceEffect]
public let errata: Errata?
public init(name: NonEmptyString, effects: [InfluenceEffect], errata: Errata? = nil) {
self.name = name
self.effects = effects
self.errata = errata
}
}
public struct InfluenceEffect: EntitySubtype {
/// An optional label that is displayed and placed before the actual text.
public let label: NonEmptyString?
/// The effect text.
public let text: NonEmptyString
public init(label: NonEmptyString? = nil, text: NonEmptyString) {
self.label = label
self.text = text
}
}
@@ -0,0 +1,8 @@
//
// _NonEmptyString.swift
// OptolithDatabaseSchema
//
public typealias NonEmptyString = String
public typealias NonEmptyMarkdown = String
@@ -0,0 +1,110 @@
//
// _Prerequisite.swift
// OptolithDatabaseSchema
//
/// A prerequisite group has no influence on validation logic. It serves as a single unit for displaying purposes, where the source uses a prerequisites item that cannot be represented as a single prerequisite.
public struct PrerequisiteGroup<T: EntitySubtype>: EntitySubtype {
public let list: [T]
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<NonEmptyString>
public init(list: [T], translations: LocaleMap<NonEmptyString>) {
self.list = list
self.translations = translations
}
}
public struct PrerequisitesDisjunction<T: EntitySubtype>: EntitySubtype {
public let list: [T]
public let displayOption: DisplayOption?
public init(list: [T], displayOption: DisplayOption? = nil) {
self.list = list
self.displayOption = displayOption
}
private enum CodingKeys: String, CodingKey {
case list = "list"
case displayOption = "display_option"
}
}
public enum PrerequisitesElement<T: EntitySubtype>: EntitySubtype {
case single(T)
case disjunction(PrerequisitesDisjunction<T>)
case group(PrerequisiteGroup<T>)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case single = "single"
case disjunction = "disjunction"
case group = "group"
}
private enum Discriminator: String, Decodable {
case single = "Single"
case disjunction = "Disjunction"
case group = "Group"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .single:
self = .single(try container.decode(T.self, forKey: .single))
case .disjunction:
self = .disjunction(try container.decode(PrerequisitesDisjunction<T>.self, forKey: .disjunction))
case .group:
self = .group(try container.decode(PrerequisiteGroup<T>.self, forKey: .group))
}
}
}
public typealias PlainPrerequisites<T: EntitySubtype> = [PrerequisitesElement<T>]
public struct PrerequisiteForLevel<T: EntitySubtype>: EntitySubtype {
public let level: Int
public let prerequisite: PrerequisitesElement<T>
public init(level: Int, prerequisite: PrerequisitesElement<T>) {
self.level = level
self.prerequisite = prerequisite
}
}
public typealias PrerequisitesForLevels<T: EntitySubtype> = [PrerequisiteForLevel<T>]
public typealias DerivedCharacteristicPrerequisites = PlainPrerequisites<DerivedCharacteristicPrerequisiteGroup>
public typealias PublicationPrerequisites = PlainPrerequisites<PublicationPrerequisiteGroup>
public typealias PlainGeneralPrerequisites = PlainPrerequisites<GeneralPrerequisiteGroup>
public typealias GeneralPrerequisites = PrerequisitesForLevels<GeneralPrerequisiteGroup>
public typealias ProfessionPrerequisites = PlainPrerequisites<ProfessionPrerequisiteGroup>
public typealias AdvantageDisadvantagePrerequisites = PrerequisitesForLevels<AdvantageDisadvantagePrerequisiteGroup>
public typealias ArcaneTraditionPrerequisites = PlainPrerequisites<ArcaneTraditionPrerequisiteGroup>
public typealias PersonalityTraitPrerequisites = PlainPrerequisites<PersonalityTraitPrerequisiteGroup>
public typealias SpellworkPrerequisites = PlainPrerequisites<SpellworkPrerequisiteGroup>
public typealias LiturgyPrerequisites = PlainPrerequisites<LiturgyPrerequisiteGroup>
public typealias InfluencePrerequisites = PlainPrerequisites<InfluencePrerequisiteGroup>
public typealias LanguagePrerequisites = PrerequisitesForLevels<LanguagePrerequisiteGroup>
public typealias AnimistPowerPrerequisites = PlainPrerequisites<AnimistPowerPrerequisiteGroup>
public typealias GeodeRitualPrerequisites = PlainPrerequisites<GeodeRitualPrerequisiteGroup>
public typealias EnhancementPrerequisites = PlainPrerequisites<EnhancementPrerequisiteGroup>
@@ -0,0 +1,46 @@
//
// _ResponsiveText.swift
// OptolithDatabaseSchema
//
/// A text from the source that can be also displayed in small areas using a compressed version.
public struct ResponsiveText: EntitySubtype {
/// The full text from the source.
public let full: NonEmptyString
/// A compressed text for use in small areas (e.g. on character sheet).
public let compressed: NonEmptyString
public init(full: NonEmptyString, compressed: NonEmptyString) {
self.full = full
self.compressed = compressed
}
}
/// A text from the source that can be also displayed in small areas using a compressed version. It is used as a replacement for a generated text while the generated text is still provided and should be used.
public struct ResponsiveTextReplace: EntitySubtype {
/// The full replacement string. It must contain `$1`, which is going to be replaced with the generated string, so additional information can be provided without duplicating concrete numeric values.
public let full: String
/// A compressed replacement string for use in small areas (e.g. on character sheet). It must contain `$1`, which is going to be replaced with the generated string, so additional information can be provided without duplicating concrete numeric values.
public let compressed: String
public init(full: String, compressed: String) {
self.full = full
self.compressed = compressed
}
}
/// A text from the source that can be also displayed in small areas using a compressed version, if available.
public struct ResponsiveTextOptional: EntitySubtype {
/// The full text from the source.
public let full: NonEmptyString
/// A compressed text for use in small areas (e.g. on character sheet). If this is not present, it should not appear in those areas.
public let compressed: NonEmptyString?
public init(full: NonEmptyString, compressed: NonEmptyString? = nil) {
self.full = full
self.compressed = compressed
}
}
@@ -0,0 +1,9 @@
//
// _Sex.swift
// OptolithDatabaseSchema
//
public enum BinarySex: String, Entity {
case male = "Male"
case female = "Female"
}
@@ -0,0 +1,356 @@
//
// _SimpleReferences.swift
// OptolithDatabaseSchema
//
public struct FocusRuleSubjectReference: EntitySubtype {
public let id: SubjectIdentifier
public init(id: SubjectIdentifier) {
self.id = id
}
}
public struct RaceReference: EntitySubtype {
public let id: RaceIdentifier
public init(id: RaceIdentifier) {
self.id = id
}
}
public struct CultureReference: EntitySubtype {
public let id: CultureIdentifier
public init(id: CultureIdentifier) {
self.id = id
}
}
public struct ProfessionReference: EntitySubtype {
public let id: ProfessionIdentifier
public init(id: ProfessionIdentifier) {
self.id = id
}
}
public struct ProfessionVariantReference: EntitySubtype {
public let id: ProfessionVariantIdentifier
public init(id: ProfessionVariantIdentifier) {
self.id = id
}
}
public struct AttributeReference: EntitySubtype {
public let id: AttributeIdentifier
public init(id: AttributeIdentifier) {
self.id = id
}
}
public struct SkillReference: EntitySubtype {
public let id: SkillIdentifier
public init(id: SkillIdentifier) {
self.id = id
}
}
public struct SkillGroupReference: EntitySubtype {
public let id: SkillGroupIdentifier
public init(id: SkillGroupIdentifier) {
self.id = id
}
}
public struct CombatTechniqueReference: EntitySubtype {
public let id: CombatTechniqueIdentifier
public init(id: CombatTechniqueIdentifier) {
self.id = id
}
}
public struct CloseCombatTechniqueReference: EntitySubtype {
public let id: CloseCombatTechniqueIdentifier
public init(id: CloseCombatTechniqueIdentifier) {
self.id = id
}
}
public struct RangedCombatTechniqueReference: EntitySubtype {
public let id: RangedCombatTechniqueIdentifier
public init(id: RangedCombatTechniqueIdentifier) {
self.id = id
}
}
public struct MagicalTraditionReference: EntitySubtype {
public let id: MagicalTraditionIdentifier
public init(id: MagicalTraditionIdentifier) {
self.id = id
}
}
public struct CantripReference: EntitySubtype {
public let id: CantripIdentifier
public init(id: CantripIdentifier) {
self.id = id
}
}
public struct SpellReference: EntitySubtype {
public let id: SpellIdentifier
public init(id: SpellIdentifier) {
self.id = id
}
}
public struct RitualReference: EntitySubtype {
public let id: RitualIdentifier
public init(id: RitualIdentifier) {
self.id = id
}
}
public struct SpellworkReference: EntitySubtype {
public let id: SpellworkIdentifier
public init(id: SpellworkIdentifier) {
self.id = id
}
}
public struct PropertyReference: EntitySubtype {
public let id: PropertyIdentifier
public init(id: PropertyIdentifier) {
self.id = id
}
}
public struct BlessedTraditionReference: EntitySubtype {
public let id: BlessedTraditionIdentifier
public init(id: BlessedTraditionIdentifier) {
self.id = id
}
}
public struct BlessingReference: EntitySubtype {
public let id: BlessingIdentifier
public init(id: BlessingIdentifier) {
self.id = id
}
}
public struct LiturgicalChantReference: EntitySubtype {
public let id: LiturgicalChantIdentifier
public init(id: LiturgicalChantIdentifier) {
self.id = id
}
}
public struct CeremonyReference: EntitySubtype {
public let id: CeremonyIdentifier
public init(id: CeremonyIdentifier) {
self.id = id
}
}
public struct AspectReference: EntitySubtype {
public let id: AspectIdentifier
public init(id: AspectIdentifier) {
self.id = id
}
}
public struct AdvantageReference: EntitySubtype {
public let id: AdvantageIdentifier
public init(id: AdvantageIdentifier) {
self.id = id
}
}
public struct DisadvantageReference: EntitySubtype {
public let id: DisadvantageIdentifier
public init(id: DisadvantageIdentifier) {
self.id = id
}
}
public struct AdvancedSpecialAbilityReference<Identifier: EntitySubtype>: EntitySubtype {
public let id: Identifier
public init(id: Identifier) {
self.id = id
}
}
public struct LanguageReference: EntitySubtype {
public let id: LanguageIdentifier
public init(id: LanguageIdentifier) {
self.id = id
}
}
public struct ScriptReference: EntitySubtype {
public let id: ScriptIdentifier
public init(id: ScriptIdentifier) {
self.id = id
}
}
public struct SocialStatusReference: EntitySubtype {
public let id: SocialStatusIdentifier
public init(id: SocialStatusIdentifier) {
self.id = id
}
}
public struct CurriculumReference: EntitySubtype {
public let id: CurriculumIdentifier
public init(id: CurriculumIdentifier) {
self.id = id
}
}
public struct GuidelineReference: EntitySubtype {
public let id: GuidelineIdentifier
public init(id: GuidelineIdentifier) {
self.id = id
}
}
public struct AnimalTypeReference: EntitySubtype {
public let id: AnimalTypeIdentifier
public init(id: AnimalTypeIdentifier) {
self.id = id
}
}
public struct TargetCategoryReference: EntitySubtype {
public let id: TargetCategoryIdentifier
public init(id: TargetCategoryIdentifier) {
self.id = id
}
}
public struct PatronCategoryReference: EntitySubtype {
public let id: PatronCategoryIdentifier
public init(id: PatronCategoryIdentifier) {
self.id = id
}
}
public struct PersonalityTraitReference: EntitySubtype {
public let id: PersonalityTraitIdentifier
public init(id: PersonalityTraitIdentifier) {
self.id = id
}
}
public struct HairColorReference: EntitySubtype {
public let id: HairColorIdentifier
public init(id: HairColorIdentifier) {
self.id = id
}
}
public struct EyeColorReference: EntitySubtype {
public let id: EyeColorIdentifier
public init(id: EyeColorIdentifier) {
self.id = id
}
}
public struct PactCategoryReference: EntitySubtype {
public let id: PactCategoryIdentifier
public init(id: PactCategoryIdentifier) {
self.id = id
}
}
public struct PactDomainReference: EntitySubtype {
public let id: PactDomainIdentifier
public init(id: PactDomainIdentifier) {
self.id = id
}
}
public struct PatronReference: EntitySubtype {
public let id: PatronIdentifier
public init(id: PatronIdentifier) {
self.id = id
}
}
public struct AnimistTribeReference: EntitySubtype {
public let id: AnimistTribeIdentifier
public init(id: AnimistTribeIdentifier) {
self.id = id
}
}
public struct ElementReference: EntitySubtype {
public let id: ElementIdentifier
public init(id: ElementIdentifier) {
self.id = id
}
}
public struct WeaponReference: EntitySubtype {
public let id: WeaponIdentifier
public init(id: WeaponIdentifier) {
self.id = id
}
}
public struct ArmorReference: EntitySubtype {
public let id: ArmorIdentifier
public init(id: ArmorIdentifier) {
self.id = id
}
}
public struct TraditionReference: EntitySubtype {
public let id: TraditionIdentifier
public init(id: TraditionIdentifier) {
self.id = id
}
}
@@ -0,0 +1,17 @@
//
// _SkillCheck.swift
// OptolithDatabaseSchema
//
/// The attributes' identifiers of the skill check.
public typealias SkillCheck = [AttributeReference]
/// A specific value that represents a penalty for the associated skill check.
public enum SkillCheckPenalty: String, EntitySubtype {
case spirit = "Spirit"
case halfOfSpirit = "HalfOfSpirit"
case toughness = "Toughness"
case higherOfSpiritAndToughness = "HigherOfSpiritAndToughness"
case summoningDifficulty = "SummoningDifficulty"
case creationDifficulty = "CreationDifficulty"
}
@@ -0,0 +1,35 @@
//
// _Spellwork.swift
// OptolithDatabaseSchema
//
/// The tradition(s) the ritual is available for. It may be *generally* available to all traditions or it may be only familiar in specific traditions.
public enum Traditions: EntitySubtype {
case general
case specific(SpecificTraditions)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case general = "general"
case specific = "specific"
}
private enum Discriminator: String, Decodable {
case general = "General"
case specific = "Specific"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .general:
self = .general
case .specific:
self = .specific(try container.decode(SpecificTraditions.self, forKey: .specific))
}
}
}
/// A list of specific traditions.
public typealias SpecificTraditions = [MagicalTraditionIdentifier]
@@ -0,0 +1,46 @@
//
// EquipmentPackage.swift
// OptolithDatabaseSchema
//
public struct EquipmentPackage: LocalizableEntity {
/// The equipment package's identifier. An unique, increasing integer.
public let id: Int
/// All items in the package. You have to provide the item (template) identifier and you can optionally provide the number of how often an item is included in the package.
public let items: [EquipmentPackageItem]
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<EquipmentPackageTranslation>
public init(id: Int, items: [EquipmentPackageItem], src: PublicationRefs, translations: LocaleMap<EquipmentPackageTranslation>) {
self.id = id
self.items = items
self.src = src
self.translations = translations
}
}
public struct EquipmentPackageItem: EntitySubtype {
/// The item's identifier.
public let id: EquipmentIdentifier
/// The number of how often the item is included in the package.
public let number: Int?
public init(id: EquipmentIdentifier, number: Int? = nil) {
self.id = id
self.number = number
}
}
public struct EquipmentPackageTranslation: EntitySubtype {
/// The name of the equipment package.
public let name: NonEmptyString
public init(name: NonEmptyString) {
self.name = name
}
}
@@ -0,0 +1,42 @@
//
// Ammunition.swift
// OptolithDatabaseSchema
//
public struct Ammunition: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<AmmunitionTranslation>
public init(cost: Cost, src: PublicationRefs, translations: LocaleMap<AmmunitionTranslation>) {
self.cost = cost
self.src = src
self.translations = translations
}
}
public struct AmmunitionTranslation: EntitySubtype {
/// The name of the item.
public let name: NonEmptyString
/// An auxiliary name or label of the item, if available.
public let secondaryName: NonEmptyString?
public let errata: Errata?
public init(name: NonEmptyString, secondaryName: NonEmptyString? = nil, errata: Errata? = nil) {
self.name = name
self.secondaryName = secondaryName
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case secondaryName = "secondary_name"
case errata = "errata"
}
}
@@ -0,0 +1,20 @@
//
// Animal.swift
// OptolithDatabaseSchema
//
public struct Animal: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DefaultItemTranslation>
public init(cost: Cost, src: PublicationRefs, translations: LocaleMap<DefaultItemTranslation>) {
self.cost = cost
self.src = src
self.translations = translations
}
}
@@ -0,0 +1,92 @@
//
// AnimalCare.swift
// OptolithDatabaseSchema
//
public struct AnimalCare: LocalizableEntity {
/// Values depending on whether the animal care is feed.
public let type: AnimalCareType
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DefaultItemTranslation>
public init(type: AnimalCareType, src: PublicationRefs, translations: LocaleMap<DefaultItemTranslation>) {
self.type = type
self.src = src
self.translations = translations
}
}
/// Values depending on whether the animal care is feed.
public enum AnimalCareType: EntitySubtype {
case general(GeneralAnimalCare)
case feed(AnimalFeed)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case general = "general"
case feed = "feed"
}
private enum Discriminator: String, Decodable {
case general = "General"
case feed = "Feed"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .general:
self = .general(try container.decode(GeneralAnimalCare.self, forKey: .general))
case .feed:
self = .feed(try container.decode(AnimalFeed.self, forKey: .feed))
}
}
}
public struct GeneralAnimalCare: EntitySubtype {
/// The cost in silverthalers.
public let cost: Cost
/// The weight in kg.
public let weight: Weight
public init(cost: Cost, weight: Weight) {
self.cost = cost
self.weight = weight
}
}
public struct AnimalFeed: EntitySubtype {
/// The cost in silverthalers.
public let cost: AnimalFeedCost
public init(cost: AnimalFeedCost) {
self.cost = cost
}
}
public enum AnimalFeedCost: EntitySubtype {
case perWeek(FixedCost)
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case perWeek = "per_week"
}
private enum Discriminator: String, Decodable {
case perWeek = "PerWeek"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .perWeek:
self = .perWeek(try container.decode(FixedCost.self, forKey: .perWeek))
}
}
}
@@ -0,0 +1,237 @@
//
// Armor.swift
// OptolithDatabaseSchema
//
public struct Armor: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
/// The weight in kg.
public let weight: Weight
/// The complexity of crafting the item.
public let complexity: Complexity
/// The PRO value.
public let protection: Protection
/// The ENC value.
public let encumbrance: Encumbrance
/// Does the armor have additional penalties (MOV -1, INI -1)?
public let hasAdditionalPenalties: HasAdditionalPenalties
/// The armor type.
public let armorType: ArmorTypeReference
/// Specify if armor is only available for a specific hit zone.
public let hitZone: HitZone?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ArmorTranslation>
public init(cost: Cost, weight: Weight, complexity: Complexity, protection: Protection, encumbrance: Encumbrance, hasAdditionalPenalties: HasAdditionalPenalties, armorType: ArmorTypeReference, hitZone: HitZone? = nil, src: PublicationRefs, translations: LocaleMap<ArmorTranslation>) {
self.cost = cost
self.weight = weight
self.complexity = complexity
self.protection = protection
self.encumbrance = encumbrance
self.hasAdditionalPenalties = hasAdditionalPenalties
self.armorType = armorType
self.hitZone = hitZone
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case cost = "cost"
case weight = "weight"
case complexity = "complexity"
case protection = "protection"
case encumbrance = "encumbrance"
case hasAdditionalPenalties = "has_additional_penalties"
case armorType = "armor_type"
case hitZone = "hit_zone"
case src = "src"
case translations = "translations"
}
}
public struct ArmorTranslation: EntitySubtype {
/// The name of the item.
public let name: NonEmptyString
/// An auxiliary name or label of the item, if available.
public let secondaryName: NonEmptyString?
/// Note text.
public let note: NonEmptyMarkdown?
/// Special rules text.
public let rules: NonEmptyMarkdown?
/// The armor advantage text.
public let advantage: NonEmptyMarkdown?
/// The armor disadvantage text.
public let disadvantage: NonEmptyMarkdown?
public let errata: Errata?
public init(name: NonEmptyString, secondaryName: NonEmptyString? = nil, note: NonEmptyMarkdown? = nil, rules: NonEmptyMarkdown? = nil, advantage: NonEmptyMarkdown? = nil, disadvantage: NonEmptyMarkdown? = nil, errata: Errata? = nil) {
self.name = name
self.secondaryName = secondaryName
self.note = note
self.rules = rules
self.advantage = advantage
self.disadvantage = disadvantage
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case secondaryName = "secondary_name"
case note = "note"
case rules = "rules"
case advantage = "advantage"
case disadvantage = "disadvantage"
case errata = "errata"
}
}
public struct SecondaryArmor: EntitySubtype {
/// The PRO value.
public let protection: Protection
/// The ENC value.
public let encumbrance: Encumbrance
/// Does the armor have additional penalties (MOV -1, INI -1)?
public let hasAdditionalPenalties: HasAdditionalPenalties
/// The armor type.
public let armorType: ArmorTypeReference
/// Specify if armor is only available for a specific hit zone.
public let hitZone: HitZone?
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<SecondaryArmorTranslation>?
public init(protection: Protection, encumbrance: Encumbrance, hasAdditionalPenalties: HasAdditionalPenalties, armorType: ArmorTypeReference, hitZone: HitZone? = nil, translations: LocaleMap<SecondaryArmorTranslation>? = nil) {
self.protection = protection
self.encumbrance = encumbrance
self.hasAdditionalPenalties = hasAdditionalPenalties
self.armorType = armorType
self.hitZone = hitZone
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case protection = "protection"
case encumbrance = "encumbrance"
case hasAdditionalPenalties = "has_additional_penalties"
case armorType = "armor_type"
case hitZone = "hit_zone"
case translations = "translations"
}
}
public struct SecondaryArmorTranslation: EntitySubtype {
/// The armor advantage text.
public let advantage: NonEmptyMarkdown?
/// The armor disadvantage text.
public let disadvantage: NonEmptyMarkdown?
public init(advantage: NonEmptyMarkdown? = nil, disadvantage: NonEmptyMarkdown? = nil) {
self.advantage = advantage
self.disadvantage = disadvantage
}
}
/// The PRO value.
public typealias Protection = Int
/// The ENC value.
public typealias Encumbrance = Int
/// Does the armor have additional penalties (MOV -1, INI -1)?
public typealias HasAdditionalPenalties = Bool
/// The armor type.
public struct ArmorTypeReference: EntitySubtype {
/// The armor type's identifier.
public let id: Int
public init(id: Int) {
self.id = id
}
}
/// Specify if armor is only available for a specific hit zone.
public enum HitZone: EntitySubtype {
case head(HeadHitZone)
case torso
case arms
case legs
private enum CodingKeys: String, CodingKey {
case tag = "tag"
case head = "head"
case torso = "torso"
case arms = "arms"
case legs = "legs"
}
private enum Discriminator: String, Decodable {
case head = "Head"
case torso = "Torso"
case arms = "Arms"
case legs = "Legs"
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let tag = try container.decode(Discriminator.self, forKey: .tag)
switch tag {
case .head:
self = .head(try container.decode(HeadHitZone.self, forKey: .head))
case .torso:
self = .torso
case .arms:
self = .arms
case .legs:
self = .legs
}
}
}
public struct HeadHitZone: EntitySubtype {
/// In some cases, multiple armors for the same hit zone can be combined. They're listed at the item that can be combined with others.
public let combinationPossibilities: HeadHitZoneCombinationPossibilities?
public init(combinationPossibilities: HeadHitZoneCombinationPossibilities? = nil) {
self.combinationPossibilities = combinationPossibilities
}
private enum CodingKeys: String, CodingKey {
case combinationPossibilities = "combination_possibilities"
}
}
public struct HeadHitZoneCombinationPossibilities: EntitySubtype {
/// A list of armors that can be combined with this armor.
public let armors: [ArmorReference]
/// The PRO value that is added to the PRO value of the other armor instead of adding the normale PRO value.
public let protection: Int?
public init(armors: [ArmorReference], protection: Int? = nil) {
self.armors = armors
self.protection = protection
}
}
@@ -0,0 +1,46 @@
//
// BandageOrRemedy.swift
// OptolithDatabaseSchema
//
public struct BandageOrRemedy: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
/// The weight in kg.
public let weight: Weight
/// The complexity of crafting the item.
public let complexity: Complexity?
/// The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.
public let structurePoints: StructurePoints?
/// 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.
public let combatUse: CombatUse?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DefaultItemTranslation>
public init(cost: Cost, weight: Weight, complexity: Complexity? = nil, structurePoints: StructurePoints? = nil, combatUse: CombatUse? = nil, src: PublicationRefs, translations: LocaleMap<DefaultItemTranslation>) {
self.cost = cost
self.weight = weight
self.complexity = complexity
self.structurePoints = structurePoints
self.combatUse = combatUse
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case cost = "cost"
case weight = "weight"
case complexity = "complexity"
case structurePoints = "structure_points"
case combatUse = "combat_use"
case src = "src"
case translations = "translations"
}
}
@@ -0,0 +1,83 @@
//
// Book.swift
// OptolithDatabaseSchema
//
public struct Book: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
/// The weight in kg.
public let weight: Weight
/// The complexity of crafting the item.
public let complexity: Complexity
/// The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.
public let structurePoints: StructurePoints
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<BookTranslation>
public init(cost: Cost, weight: Weight, complexity: Complexity, structurePoints: StructurePoints, src: PublicationRefs, translations: LocaleMap<BookTranslation>) {
self.cost = cost
self.weight = weight
self.complexity = complexity
self.structurePoints = structurePoints
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case cost = "cost"
case weight = "weight"
case complexity = "complexity"
case structurePoints = "structure_points"
case src = "src"
case translations = "translations"
}
}
public struct BookTranslation: EntitySubtype {
/// The name of the item.
public let name: NonEmptyString
/// An auxiliary name or label of the item, if available.
public let secondaryName: NonEmptyString?
/// The language the book is written in.
public let language: NonEmptyString
/// The script that was used for the book.
public let script: NonEmptyString
/// Note text.
public let note: NonEmptyMarkdown?
/// Special rules text.
public let rules: NonEmptyMarkdown?
public let errata: Errata?
public init(name: NonEmptyString, secondaryName: NonEmptyString? = nil, language: NonEmptyString, script: NonEmptyString, note: NonEmptyMarkdown? = nil, rules: NonEmptyMarkdown? = nil, errata: Errata? = nil) {
self.name = name
self.secondaryName = secondaryName
self.language = language
self.script = script
self.note = note
self.rules = rules
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case secondaryName = "secondary_name"
case language = "language"
case script = "script"
case note = "note"
case rules = "rules"
case errata = "errata"
}
}
@@ -0,0 +1,51 @@
//
// CeremonialItem.swift
// OptolithDatabaseSchema
//
public struct CeremonialItem: LocalizableEntity {
/// The cost in silverthalers.
public let cost: Cost
/// The weight in kg.
public let weight: Weight
/// The complexity of crafting the item.
public let complexity: Complexity?
/// The structure points of the item. Use an array if the item consists of multiple components that have individual structure points.
public let structurePoints: StructurePoints
/// The deity associated with the equipment item.
public let associatedTradition: BlessedTraditionReference
/// 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.
public let combatUse: CombatUse?
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<DefaultItemTranslation>
public init(cost: Cost, weight: Weight, complexity: Complexity? = nil, structurePoints: StructurePoints, associatedTradition: BlessedTraditionReference, combatUse: CombatUse? = nil, src: PublicationRefs, translations: LocaleMap<DefaultItemTranslation>) {
self.cost = cost
self.weight = weight
self.complexity = complexity
self.structurePoints = structurePoints
self.associatedTradition = associatedTradition
self.combatUse = combatUse
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case cost = "cost"
case weight = "weight"
case complexity = "complexity"
case structurePoints = "structure_points"
case associatedTradition = "associated_tradition"
case combatUse = "combat_use"
case src = "src"
case translations = "translations"
}
}
@@ -0,0 +1,6 @@
//
// Clothes.swift
// OptolithDatabaseSchema
//
public typealias Clothes = DefaultItem
@@ -0,0 +1,6 @@
//
// Container.swift
// OptolithDatabaseSchema
//
public typealias Container = DefaultItem
@@ -0,0 +1,83 @@
//
// Elixir.swift
// OptolithDatabaseSchema
//
public struct Elixir: LocalizableEntity {
/// The elixir's identifier. An unique, increasing integer.
public let id: Int
/// The cost per ingredient level in silverthalers.
public let costPerIngredientLevel: Double
/// The laboratory level needed to brew the elixir.
public let laboratory: LaboratoryLevel
/// The brewing difficulty, which represents the challenge of creating an elixir.
public let brewingDifficulty: Int
/// AP value and prerequisites of the elixir recipes trade secret.
public let tradeSecret: RecipeTradeSecret
public let src: PublicationRefs
/// All translations for the entry, identified by IETF language tag (BCP47).
public let translations: LocaleMap<ElixirTranslation>
public init(id: Int, costPerIngredientLevel: Double, laboratory: LaboratoryLevel, brewingDifficulty: Int, tradeSecret: RecipeTradeSecret, src: PublicationRefs, translations: LocaleMap<ElixirTranslation>) {
self.id = id
self.costPerIngredientLevel = costPerIngredientLevel
self.laboratory = laboratory
self.brewingDifficulty = brewingDifficulty
self.tradeSecret = tradeSecret
self.src = src
self.translations = translations
}
private enum CodingKeys: String, CodingKey {
case id = "id"
case costPerIngredientLevel = "cost_per_ingredient_level"
case laboratory = "laboratory"
case brewingDifficulty = "brewing_difficulty"
case tradeSecret = "trade_secret"
case src = "src"
case translations = "translations"
}
}
public struct ElixirTranslation: EntitySubtype {
/// The name of the elixir.
public let name: NonEmptyString
/// A list of alternative names.
public let alternativeNames: [AlternativeName]?
/// A list of typical ingredients.
public let typicalIngredients: [NonEmptyString]
/// Prerequsites for the brewing process, if any.
public let brewingProcessPrerequisites: NonEmptyMarkdown?
/// The list of effects for each quality level. The first element represents QL 1, the second element QL 2, and so on.
public let qualityLevels: [NonEmptyMarkdown]
public let errata: Errata?
public init(name: NonEmptyString, alternativeNames: [AlternativeName]? = nil, typicalIngredients: [NonEmptyString], brewingProcessPrerequisites: NonEmptyMarkdown? = nil, qualityLevels: [NonEmptyMarkdown], errata: Errata? = nil) {
self.name = name
self.alternativeNames = alternativeNames
self.typicalIngredients = typicalIngredients
self.brewingProcessPrerequisites = brewingProcessPrerequisites
self.qualityLevels = qualityLevels
self.errata = errata
}
private enum CodingKeys: String, CodingKey {
case name = "name"
case alternativeNames = "alternative_names"
case typicalIngredients = "typical_ingredients"
case brewingProcessPrerequisites = "brewing_process_prerequisites"
case qualityLevels = "quality_levels"
case errata = "errata"
}
}

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