Compare commits
262
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91164ca39 | ||
|
|
594b3aae55 | ||
|
|
49caf9fa07 | ||
|
|
7759ea78e6 | ||
|
|
788be838c2 | ||
|
|
ee3a8c454f | ||
|
|
908055f69c | ||
|
|
2194dab596 | ||
|
|
6eb89a518a | ||
|
|
9c81d5e939 | ||
|
|
b48dded524 | ||
|
|
9b5a73ccf9 | ||
|
|
ddde8957b2 | ||
|
|
dd3caf631d | ||
|
|
3df0a65b2d | ||
|
|
785c2e26ce | ||
|
|
427fa14b67 | ||
|
|
2c13926d07 | ||
|
|
c40b9e6fd6 | ||
|
|
64d72a787e | ||
|
|
2d0f66ae11 | ||
|
|
9e9622a3e2 | ||
|
|
0b75d1e51f | ||
|
|
7d9d3ed449 | ||
|
|
593eb4dba7 | ||
|
|
b1373e6aaf | ||
|
|
accc5a4ea5 | ||
|
|
3eb61cc55c | ||
|
|
f7e5e06ff2 | ||
|
|
35048e1032 | ||
|
|
9ee4626f49 | ||
|
|
4f698af39b | ||
|
|
7e055864a4 | ||
|
|
669e2cb1b0 | ||
|
|
b6971d190b | ||
|
|
b0f1869886 | ||
|
|
74f7811f18 | ||
|
|
66a5329331 | ||
|
|
dfe9e37ae9 | ||
|
|
c41d3c4912 | ||
|
|
956cf3958d | ||
|
|
1e31e7b8de | ||
|
|
6776e9582a | ||
|
|
e7e02c025c | ||
|
|
e0b8625a4b | ||
|
|
7ee370c687 | ||
|
|
ced296b47f | ||
|
|
832da43ecd | ||
|
|
a62195f390 | ||
|
|
4da9621b3e | ||
|
|
e15bf1784c | ||
|
|
a35611b4d9 | ||
|
|
390dcb616a | ||
|
|
8055218c8f | ||
|
|
2cb392e511 | ||
|
|
8392e368ff | ||
|
|
692e4011b5 | ||
|
|
bcd3bd0a64 | ||
|
|
93f95c76be | ||
|
|
1af9bf192f | ||
|
|
53fbaad5c1 | ||
|
|
4c9caef67a | ||
|
|
ae652e2e60 | ||
|
|
f18a032ce6 | ||
|
|
09f98bc094 | ||
|
|
dd77fc95dd | ||
|
|
f63ea3c2d9 | ||
|
|
32aa20a890 | ||
|
|
4bbe18092c | ||
|
|
acc58c37b0 | ||
|
|
fdbdb24dee | ||
|
|
42787b8912 | ||
|
|
7be6ffdd71 | ||
|
|
9ac3fed26e | ||
|
|
f366309fb6 | ||
|
|
21b5a4d568 | ||
|
|
9baac1a58e | ||
|
|
c35f238fd7 | ||
|
|
a40e825f36 | ||
|
|
96ef705585 | ||
|
|
c441e1696b | ||
|
|
2a4a2bcf8f | ||
|
|
c21a48866c | ||
|
|
349c83b6e4 | ||
|
|
8d566f5bd1 | ||
|
|
775d35377f | ||
|
|
b24742206c | ||
|
|
f630a28797 | ||
|
|
c6ee377057 | ||
|
|
5ea3820113 | ||
|
|
acb6177317 | ||
|
|
8c143fe12f | ||
|
|
51abcdad5f | ||
|
|
7e459970cc | ||
|
|
88f03e0e21 | ||
|
|
b70c94358c | ||
|
|
28ac73c7cb | ||
|
|
3078f1f465 | ||
|
|
941acabc7d | ||
|
|
c6e36ed96b | ||
|
|
23e413b53f | ||
|
|
d8a809bf92 | ||
|
|
7e7c468dca | ||
|
|
5c3f0c27f9 | ||
|
|
ee8033b62c | ||
|
|
b5c9ede02a | ||
|
|
63c93204ac | ||
|
|
95d6994c65 | ||
|
|
8ce6767b58 | ||
|
|
5da07d8638 | ||
|
|
48bd37ae3e | ||
|
|
c52d5a4897 | ||
|
|
4123cc66e3 | ||
|
|
0cbde7c3cc | ||
|
|
13921f06c3 | ||
|
|
ab8e5ed632 | ||
|
|
be7e174353 | ||
|
|
91f6ccbdaa | ||
|
|
6bee4968cf | ||
|
|
51b2fd85fe | ||
|
|
785366f25a | ||
|
|
2e488573f0 | ||
|
|
a91241b759 | ||
|
|
13e90fe2ca | ||
|
|
0da83cb257 | ||
|
|
0fadd70dbd | ||
|
|
cbcebc8722 | ||
|
|
5b2be2aba6 | ||
|
|
fb26257254 | ||
|
|
6141671c11 | ||
|
|
8983c129be | ||
|
|
f0fedb2ee5 | ||
|
|
a8cc0883df | ||
|
|
0820860125 | ||
|
|
ab1cf43a37 | ||
|
|
d63375084a | ||
|
|
1289d95391 | ||
|
|
dbcfcdfdcb | ||
|
|
daf95e35b3 | ||
|
|
f6c16227df | ||
|
|
a6ee74dfd8 | ||
|
|
66d76426a7 | ||
|
|
6b281c4057 | ||
|
|
c01d91c187 | ||
|
|
22da727071 | ||
|
|
11ffa12eb7 | ||
|
|
2b5b87f820 | ||
|
|
b0f7feca02 | ||
|
|
d223d380cb | ||
|
|
4130eb180d | ||
|
|
549b98b67f | ||
|
|
56c83ee4b5 | ||
|
|
eeb94be654 | ||
|
|
bc77542759 | ||
|
|
aa66e9cab6 | ||
|
|
637e8965d3 | ||
|
|
151370fb77 | ||
|
|
1fd8caaafc | ||
|
|
2b36e78fcd | ||
|
|
0d4f6d3a36 | ||
|
|
074f75a033 | ||
|
|
d9b8223ade | ||
|
|
875fe0e948 | ||
|
|
655658c59a | ||
|
|
872728bb35 | ||
|
|
286c0babc5 | ||
|
|
88204a54a1 | ||
|
|
76b2183954 | ||
|
|
43bdc12cb7 | ||
|
|
3cef003549 | ||
|
|
9cbb70ba52 | ||
|
|
bd7161062c | ||
|
|
080670ced1 | ||
|
|
aec0cac9ac | ||
|
|
815f4e6344 | ||
|
|
cf4b44b5b0 | ||
|
|
a61fa495b3 | ||
|
|
fde91bdcab | ||
|
|
16e39566cc | ||
|
|
7ac849be21 | ||
|
|
fab2c017b8 | ||
|
|
a608e925df | ||
|
|
49dd8ab6be | ||
|
|
6a422fa528 | ||
|
|
35e63a40b1 | ||
|
|
31522b85b4 | ||
|
|
d0960fa4f8 | ||
|
|
a46bdd7fff | ||
|
|
5276dea8c4 | ||
|
|
526a80d168 | ||
|
|
06d175904a | ||
|
|
b3ea4e62b0 | ||
|
|
82baf13397 | ||
|
|
152e2a133f | ||
|
|
445f67fd06 | ||
|
|
dcdbd15118 | ||
|
|
b086de5066 | ||
|
|
666c729e43 | ||
|
|
e5aed52707 | ||
|
|
669ce2057d | ||
|
|
7fd7d277c7 | ||
|
|
3d750ce323 | ||
|
|
4bc8bb8ebf | ||
|
|
f9a13d56bc | ||
|
|
34c72bf6ac | ||
|
|
e4bb5385f0 | ||
|
|
25cddaddc5 | ||
|
|
78f10da735 | ||
|
|
3403502004 | ||
|
|
910a006c28 | ||
|
|
8b4cb2b95d | ||
|
|
ad4f2ecf16 | ||
|
|
c04ce290ec | ||
|
|
cfc8dc65fb | ||
|
|
dad8ffe2b7 | ||
|
|
99031a2200 | ||
|
|
174f4d9cc2 | ||
|
|
5e6ec3290c | ||
|
|
8995884070 | ||
|
|
f48ff3bec8 | ||
|
|
f01ab59b67 | ||
|
|
1b5c718295 | ||
|
|
6162e811c5 | ||
|
|
635c11cda2 | ||
|
|
57032d78bc | ||
|
|
2cad1dd6b6 | ||
|
|
a89fd07252 | ||
|
|
f2380eb9b4 | ||
|
|
3348c08808 | ||
|
|
4492304173 | ||
|
|
9acffd878e | ||
|
|
1667683ceb | ||
|
|
ae44828365 | ||
|
|
3f48a8f42d | ||
|
|
5d341bb6b6 | ||
|
|
d4f15baf96 | ||
|
|
856c7da6cf | ||
|
|
bfd1f05c89 | ||
|
|
de582f9140 | ||
|
|
f79c84caeb | ||
|
|
521bca34fe | ||
|
|
d616157397 | ||
|
|
b8cd0794ca | ||
|
|
7ec14231e0 | ||
|
|
053fcb2874 | ||
|
|
9b7948f6f6 | ||
|
|
d31be41a12 | ||
|
|
117a732594 | ||
|
|
6fe2c3323f | ||
|
|
b888851995 | ||
|
|
9b95187936 | ||
|
|
283825d6d9 | ||
|
|
8da9b6f641 | ||
|
|
2b20c8598a | ||
|
|
58df332c06 | ||
|
|
b1f9a155f9 | ||
|
|
2bc235292c | ||
|
|
1eae92cabc | ||
|
|
dd3077164e | ||
|
|
5dbf47af8e | ||
|
|
df6c9c310e | ||
|
|
e653063350 |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
+12
-3
@@ -93,7 +93,7 @@ rules:
|
||||
no-octal-escape: 2
|
||||
no-param-reassign: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 2
|
||||
no-redeclare: 0
|
||||
no-return-assign: 2
|
||||
no-script-url: 2
|
||||
no-self-assign: 2
|
||||
@@ -120,7 +120,7 @@ rules:
|
||||
init-declarations: 2
|
||||
no-delete-var: 2
|
||||
no-label-var: 2
|
||||
no-shadow: 2
|
||||
no-shadow: 0
|
||||
no-shadow-restricted-names: 2
|
||||
# no-undef: 2
|
||||
|
||||
@@ -307,7 +307,6 @@ rules:
|
||||
"@typescript-eslint/await-thenable": 2
|
||||
# "@typescript-eslint/class-name-casing": 2
|
||||
"@typescript-eslint/consistent-type-assertions": 2
|
||||
"@typescript-eslint/interface-name-prefix": 2
|
||||
"@typescript-eslint/member-delimiter-style":
|
||||
- 2
|
||||
- multiline:
|
||||
@@ -315,6 +314,14 @@ rules:
|
||||
singleline:
|
||||
delimiter: semi
|
||||
requireLast: false
|
||||
"@typescript-eslint/naming-convention":
|
||||
- error
|
||||
- selector: interface
|
||||
format:
|
||||
- PascalCase
|
||||
custom:
|
||||
regex: "^I[A-Z]"
|
||||
match: false
|
||||
"@typescript-eslint/no-floating-promises": 2
|
||||
"@typescript-eslint/no-for-in-array": 2
|
||||
"@typescript-eslint/no-inferrable-types": 2
|
||||
@@ -361,6 +368,8 @@ rules:
|
||||
no-extra-semi: 0
|
||||
"@typescript-eslint/no-extra-semi": 2
|
||||
no-unused-expressions: 0
|
||||
"@typescript-eslint/no-redeclare": 2
|
||||
"@typescript-eslint/no-shadow": 2
|
||||
"@typescript-eslint/no-unused-expressions": 2
|
||||
"@typescript-eslint/no-unused-vars-experimental":
|
||||
- 2
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Commitlint
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
name: Lint commit messages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@v2
|
||||
with:
|
||||
configFile: .commitlintrc.json
|
||||
@@ -36,7 +36,9 @@ jobs:
|
||||
- name: Build ReasonML files
|
||||
run: npm run re:build
|
||||
- name: Build TypeScript files
|
||||
run: npm run ts:build
|
||||
run: npm run js:build
|
||||
- name: Build Sass files
|
||||
run: npm run css:build
|
||||
- name: Build
|
||||
run: node ./deploy/build.js prerelease
|
||||
env:
|
||||
|
||||
@@ -36,7 +36,9 @@ jobs:
|
||||
- name: Build ReasonML files
|
||||
run: npm run re:build
|
||||
- name: Build TypeScript files
|
||||
run: npm run ts:build
|
||||
run: npm run js:build
|
||||
- name: Build Sass files
|
||||
run: npm run css:build
|
||||
- name: Build
|
||||
run: node ./deploy/build.js stable
|
||||
env:
|
||||
|
||||
@@ -3,9 +3,11 @@ name: Integration
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
@@ -18,10 +20,10 @@ jobs:
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 12.x
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- run: npm run re:build
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
_opam
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
.vs/
|
||||
@@ -6,6 +7,8 @@ npm-debug.log*
|
||||
/dist/
|
||||
.rpt2_cache
|
||||
.esm-cache
|
||||
app/index.html
|
||||
app/main.css
|
||||
app/**/*.js
|
||||
app/**/*.js.map
|
||||
!app/index.js
|
||||
@@ -14,12 +17,15 @@ app/**/*.js.map
|
||||
electron-builder.env
|
||||
*.map
|
||||
publishSettings.json
|
||||
**/*.bs.js
|
||||
|
||||
.env*
|
||||
|
||||
Tables.zip
|
||||
tables/
|
||||
deploy/tablesSrc.json
|
||||
/_esy
|
||||
/esy.lock
|
||||
/lib/bs/
|
||||
.merlin
|
||||
.bsb.lock
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
[submodule "app/Database"]
|
||||
path = app/Database
|
||||
path = src/Database
|
||||
url = https://github.com/elyukai/optolith-data.git
|
||||
|
||||
Generated
+24
@@ -0,0 +1,24 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Bucklescript" type="javaScript">
|
||||
<properties>
|
||||
<sourceFilesUrls>
|
||||
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/reason-react" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/reductive" />
|
||||
</sourceFilesUrls>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/reason-react" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/reductive" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/src/Database" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,2 @@
|
||||
break-infix-before-func = true
|
||||
break-collection-expressions = wrap
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"types": [
|
||||
{ "type": "feat", "section": "Features" },
|
||||
{ "type": "fix", "section": "Bug Fixes" },
|
||||
{ "type": "build", "hidden": true },
|
||||
{ "type": "chore", "hidden": true },
|
||||
{ "type": "ci", "hidden": true },
|
||||
{ "type": "docs", "hidden": true },
|
||||
{ "type": "style", "hidden": true },
|
||||
{ "type": "refactor", "hidden": true },
|
||||
{ "type": "perf", "hidden": true },
|
||||
{ "type": "test", "hidden": true }
|
||||
],
|
||||
"releaseCommitMessageFormat": "build(release): {{currentTag}}"
|
||||
}
|
||||
Vendored
+2
-2
@@ -14,7 +14,7 @@
|
||||
"--remote-debugging-port=9223",
|
||||
"."
|
||||
],
|
||||
"outputCapture": "std"
|
||||
"outputCapture": "std",
|
||||
},
|
||||
{
|
||||
"name": "Electron: Attach Renderer",
|
||||
@@ -33,5 +33,5 @@
|
||||
"Electron: Attach Renderer"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
Vendored
+7
-1
@@ -17,5 +17,11 @@
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.lintTask.options": "\"src/**\" -c \"./.eslintrc.yaml\" --ext=\"ts\" --ext=\"tsx\" --ignore-pattern=\"./src/App/Utilities/YAML/Schema/**\"",
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false
|
||||
"editor.detectIndentation": false,
|
||||
"[ocaml]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[rescript]": {
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+16
-59
@@ -5,67 +5,20 @@
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"type": "shell",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "npm run build",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "buildTest",
|
||||
"command": "npm run buildTest"
|
||||
},
|
||||
{
|
||||
"label": "buildMac",
|
||||
"command": "npm run buildMac"
|
||||
},
|
||||
{
|
||||
"label": "buildMacTest",
|
||||
"command": "npm run buildMacTest"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "npm run watch"
|
||||
},
|
||||
{
|
||||
"label": "bundle",
|
||||
"command": "npm run bundle",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "run",
|
||||
"command": "npm start"
|
||||
},
|
||||
{
|
||||
"label": "bundle_l10ns",
|
||||
"command": "npm run bundle_l10ns"
|
||||
},
|
||||
{
|
||||
"label": "bundle_data",
|
||||
"command": "npm run bundle_data"
|
||||
},
|
||||
{
|
||||
"label": "bundle_css",
|
||||
"command": "npm run bundle_css"
|
||||
},
|
||||
{
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"label": "re:watch",
|
||||
"type": "shell",
|
||||
"command": "npm run re:watch",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ts:watch",
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"option": "watch",
|
||||
@@ -73,12 +26,16 @@
|
||||
"problemMatcher": [
|
||||
"$tsc-watch"
|
||||
],
|
||||
"group": "build"
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "buildWin",
|
||||
"label": "watch",
|
||||
"dependsOn": ["re:watch", "ts:watch"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
+789
@@ -0,0 +1,789 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [1.4.2](https://github.com/elyukai/optolith-client/compare/v1.4.1...v1.4.2) (2020-11-08)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Start-up was way slower than in 1.3.2. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Added some missing UI translations. [#723](https://github.com/elyukai/optolith-client/issues/723)
|
||||
- Added missing aspects for **Gods of Aventuria I**. [#724](https://github.com/elyukai/optolith-client/issues/724)
|
||||
- Added missing special ability groups for **Gods of Aventuria I**. [#725](https://github.com/elyukai/optolith-client/issues/725)
|
||||
- SA *Two Blessings at Once* had the wrong rules text. [#750](https://github.com/elyukai/optolith-client/issues/750)
|
||||
- Combat Techniques maximum CtR could be higher that allowed by rules. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- The top margin of overlay windows – like when adding advantages – was too small. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- If a profession allowed to select a second combat technique from a set of combat techniques where the bonus was lower than the bonus for the first combat technique, the selected lower bonus was not applied to the character. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- The *Torch* had P+T STR 15 instead of STR 14. [#752](https://github.com/elyukai/optolith-client/issues/752) [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- The P+T damage bonus was not added to weapons without flat damage. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- Profession *White Mage (Gareth Academy of Sword and Staff)* from the **Core Rules** had a wrong AP value and *Astronomy 4* instead of *Alchemy 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Creature skills text field on PDF export was too short to fit all relevant skills of a creature. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- SA *Followers of the Golden One* allowed liturgical chants up to improvement cost C instead of up to B. [#754](https://github.com/elyukai/optolith-client/issues/754)
|
||||
- Profession *Guildless Mage (Vadif sal Karim’s Student)* had a bonus on the wrong *Hailstorm and Roar of Thunder* spellwork. [#772](https://github.com/elyukai/optolith-client/issues/772)
|
||||
- SA *Bladestorm* had wrong applicable combat techniques. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Some books were missing the weight. [#760](https://github.com/elyukai/optolith-client/issues/760)
|
||||
- Images with uppercase file endings have not been accepted as portrait images. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- The upload icon was shown in place of the portrait image on the character sheet if no portrait was defined. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- On the Mac, if paper background was activated, additional blank pages were generated for the PDF output. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to all of you who give feedback and help out on various channels as well as to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## [1.4.1](https://github.com/elyukai/optolith-client/compare/v1.4.0...v1.4.1) (2020-09-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Italian was not available.
|
||||
|
||||
## [1.4.0](https://github.com/elyukai/optolith-client/compare/v1.3.2...v1.4.0) (2020-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
- **Gods of Aventuria I** [#656](https://github.com/elyukai/optolith-client/issues/656)
|
||||
- Italian translation [#667](https://github.com/elyukai/optolith-client/issues/667) [#610](https://github.com/elyukai/optolith-client/issues/610)
|
||||
- Optional paper-style background for character sheet. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Character Sheet view zoom option. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Add/Subtract money from purse. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
- Create custom advantages, disadvantages and special abilities with custom name and AP value (temporary solution). [#632](https://github.com/elyukai/optolith-client/issues/632)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Weight is now shown in an own column in equipment tab. [#305](https://github.com/elyukai/optolith-client/issues/305)
|
||||
- Unnecessary combat sheets are not used anymore, it now depends on which armor the character has. It defaults to the normal armor if no armor is present. [#407](https://github.com/elyukai/optolith-client/issues/407)
|
||||
- You can now click on entry names as well to show it's rules text. [#556](https://github.com/elyukai/optolith-client/issues/556)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Variants of profession *Blessed One of Boron* had wrong skill modifications. [#548](https://github.com/elyukai/optolith-client/issues/548)
|
||||
- Profession *White Mage (Sword & Staff)* had a wrong AP value and an additional skill modification. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Fixed common professions of a culture will now be shown even if they are not from the **Core Rules**. [#563](https://github.com/elyukai/optolith-client/issues/563)
|
||||
- The interface could be scaled down. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Weapons of the combat technique *Lances* were not shown on character sheet. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Attributes ignored the minimum caused by high skill ratings. The maximum of skill rating is the highest linked attribute + 2, so the highest attribute is required to have a minimum value of SR − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- The new application *Professional Publications* for skill *Law* had a wrong special ability prerequisite. [#680](https://github.com/elyukai/optolith-client/issues/680)
|
||||
- Some list items were wider than the actual list. [#683](https://github.com/elyukai/optolith-client/pull/683)
|
||||
- List headers were not centered by default. [#637](https://github.com/elyukai/optolith-client/issues/637)
|
||||
- Permanently spent LP did not allow additional LP to be bought. [#606](https://github.com/elyukai/optolith-client/issues/606)
|
||||
- Additional LP could not be added if CON is 8 and nothing depends on it. [#694](https://github.com/elyukai/optolith-client/issues/694)
|
||||
- SA *Aspect Knowledge* did not show up even with three chants on SR 10 or higher. [#591](https://github.com/elyukai/optolith-client/issues/591)
|
||||
- Cantrip Witch's Gaze was selectable by all spell casters. [#377](https://github.com/elyukai/optolith-client/issues/377)
|
||||
- Arcane Dancers could not add magical dances. [#594](https://github.com/elyukai/optolith-client/issues/594)
|
||||
- Select options of traditions Arcane Bard and Arcane Dancer did not require their respective culture and possible sex. [#565](https://github.com/elyukai/optolith-client/issues/565)
|
||||
- If, in addition to the selection of one or more fighting techniques for a certain CtR, one or more other fighting techniques should be selected for a lower CtR (e.g. in the profession *Soldier*), it was not possible. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- Certain values on character sheet were not floored. [#655](https://github.com/elyukai/optolith-client/issues/655)
|
||||
- Chain Mail was available twice. [#570](https://github.com/elyukai/optolith-client/issues/570)
|
||||
- Scale Mail was available twice and had a typo. [#569](https://github.com/elyukai/optolith-client/issues/569)
|
||||
- Updater checks and the check for updates button are now disabled on macOS since updates are not possible on macOS currently. [#589](https://github.com/elyukai/optolith-client/issues/589)
|
||||
- Wrong applicable combat techniques for SA *Enemy Sense*. [#658](https://github.com/elyukai/optolith-client/issues/658)
|
||||
- Auto-update alerts were enabled on Linux even when there were no direct auto-updates available. [#573](https://github.com/elyukai/optolith-client/issues/573)
|
||||
- The save dialog for exporting a character as JSON did not add the file extension to the suggested file name. [#718](https://github.com/elyukai/optolith-client/issues/718)
|
||||
|
||||
### API
|
||||
|
||||
There are important and breaking changes coming with the next release, so if you use the Optolith source or save data, I'd recommend to come over to [Discord](https://discord.gg/wfdgB9g) so that I can tell you what is going to be changed in detail. Since more and more people depend on Optolith's data I will now follow semantic versioning strictly so that you can rely on the compatibility of source files just by checking out the version number. There will be a new format for heroes as well as for the source data. You can also give feedback on the changes, since they are not *completely* done yet – but I still think it would make sense to check out the changes as early as possible. The next major version is still a bit away, so you'll have plenty of time to adjust your software.
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## [1.3.2](https://github.com/elyukai/optolith-client/compare/v1.3.1...v1.3.2) (2020-05-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Combat Techniques from books other than Core Rules were missing in combat techniques tab. #543
|
||||
- Description of combat technique selection for profession variants does not render properly. #539
|
||||
- Profession *Lanisto*: Skill Specialization selection referenced the wrong skill. #537
|
||||
- Professions did not react to dependencies. #507
|
||||
- *Old Adersin Style* missing from **Aventuria Armory**. #499
|
||||
- Professions' section header "Combat Techniques" is German ("Kampftechniken"). #500
|
||||
- Textfield „Title“ in personal data had wrong label. #492
|
||||
- Profession *Hazaqi*: *Earthencraft* 4 is now *Clothworking* 4. #496
|
||||
- Trade Secrets from *Armory of the Warring Kingdoms* were missing. #474
|
||||
- SA *Binding of the Spell-Sword* had no costs. #490
|
||||
- *Religion* was on SR 6 for most of the Blessed One professions. #475
|
||||
- States were overflowing on character sheet PDF but not in Optolith. #473
|
||||
- The `config.json` has still thrown errors for many users. The problem should be fixed, finally. #476
|
||||
- In very rare situations parts of the heroes file or the whole heroes file have been deleted. Because of this, saving heroes has been completely rewritten and condensed to prevent further issues. #477
|
||||
- Added missing Garethi dialect *Nostrian*. #480
|
||||
- Fixed prerequisites for SA Energy Control. #471
|
||||
- Fixed third-party licenses. #471
|
||||
|
||||
### Known Issues
|
||||
|
||||
- Some of the entries with more complex prerequisites might *display* them wrong but *handle* them correct.
|
||||
|
||||
## [1.3.1](https://github.com/elyukai/optolith-client/compare/v1.3.0...v1.3.1) (2020-03-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- In the French version, you were not able to create characters, because you could not choose any publication, race, ... #449
|
||||
- There was an error thrown by "unknown keys" in the config. #450
|
||||
- On character sheet, states were only displayed in one column if the amount of states was even. #448
|
||||
|
||||
## [1.3.0](https://github.com/elyukai/optolith-client/compare/v1.2.0...v1.3.0) (2020-03-03)
|
||||
|
||||
### New Features
|
||||
|
||||
- French version (incl. Core Rules) #34
|
||||
|
||||
### Enhancements
|
||||
|
||||
- The search bar in wiki will not be cleared anymore if you switch the category. #286
|
||||
- If importing a hero fails you'll now get more detailed information about the cause of the problem. (Incompatible version, invalid file, ...) #343 #394
|
||||
- Completely new database layer and thus start-up time reduction up to 50%
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- The cache for AP is now working properly after not saving all of the heroes. #416
|
||||
- Languages overflow on character sheet if Hjaldingsh is active. #419
|
||||
- Personal data font is truncated vertically on character sheet. #413
|
||||
- Profession *Black Mage (Demirion Ophenos's Student)* had wrong AP cost. #344
|
||||
- Fixed weight total alignment on belongings sheet. #291
|
||||
- An item could not be set as an improvised weapon. #290
|
||||
- Routine Checks table: wrong alignment. #214
|
||||
- Afraid of: Selection/Custom text did not appear on character sheet. #213
|
||||
- Could not upload profile image from windows network share. #229
|
||||
- Adding AP did not work if you didn't leave the input field. #341
|
||||
- Weapons that do not apply damage only showed "D" as Damage. #353
|
||||
- There was no Skill Rating decrease possible for *Tracking* with SA Hunter. #357
|
||||
- SA *Onslaught* lists CT *Swords* twice. #355
|
||||
- Could not read exported hero with BOM. #370
|
||||
- Item changes could be saved in certain situations. #389
|
||||
- Fixed field "Carried where" on items not persisted. #369
|
||||
- The maximum of CTs caused by their primary attributes has been fixed. #379
|
||||
- Cast-Net has had the CT *Slings* instead of *Thrown Weapons*. #345
|
||||
- The app config does not cause errors anymore. #358
|
||||
|
||||
### API
|
||||
|
||||
As of now, there is a simple scheme for validating a hero available on [GitHub](https://github.com/elyukai/optolith-client/tree/master/app/Schema/Hero). A complete schema is in the works; the unfinished version thereof can also be found in the linked folder.
|
||||
|
||||
## [1.2.0](https://github.com/elyukai/optolith-client/compare/v1.1.2...v1.2.0) (2019-12-12)
|
||||
|
||||
This release marks a big milestone for the English version as it includes all rule books released so far. This includes the **Compendium I**, **Magic of Aventuria** and more. Also, there are quite a few fixes.
|
||||
|
||||
### New Features
|
||||
|
||||
- Dutch localization (including Core Rules)
|
||||
- **Aventuria Compendium I** English localization
|
||||
- **Magic of Aventuria I** English localization
|
||||
- **The Warring Kingdoms** English localization
|
||||
- **Armory of The Warring Kingdoms** English localization
|
||||
- **Aventuria Armory I** English localization
|
||||
- **Aventuria Bestiary I** English localization
|
||||
- **Inns & Taverns** English localization
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Shields are usually not used as the primary weapon, so the doubled PA bonus is now used on character sheet.
|
||||
- Improved Dodge I–III should not modify DO value as this should only happen when the hero does not wear armor.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Does not increase DO by 2 or 4 but 1 or 2 when optional rule *Higher Parade Values* is active.
|
||||
- Disadvantage *Bad Luck I-III* did not decrease FtP on character sheet.
|
||||
- Window for permanent loss of LP/AE/KP is now closable.
|
||||
- Disadvantage *Stigma (Green Hair)* now makes the hair color green.
|
||||
- Preview of images only displayed .jpg, no .png.
|
||||
- Native Tongue (NT) cannot be selected as a level for languages.
|
||||
- App close button did not work.
|
||||
- Removed error message on import/export cancellation.
|
||||
- *Personality Flaw*: It is now possible to buy as many Prejudices/Unworldly's as you like.
|
||||
- Required attribute values for professions were not ensured.
|
||||
- SA *Property Knowledge* did not work and had wrong AP cost.
|
||||
- Demigods were not available as applications for *Religions* skill.
|
||||
- Rerolling weight without a set size did not reroll both but instead used 0 as size.
|
||||
- Wrong carrying capacity when STR was 8.
|
||||
- CT adjustments in profession variants are not displayed correctly.
|
||||
- The default file name in the save dialog for the PDF now is the hero's name.
|
||||
- Unfamiliar Spells maximum was still active after finishing hero creation.
|
||||
- Hide characters where the version of them is not supported.
|
||||
- When selecting a spell for Tradition Guild Mage, General spells are now hidden.
|
||||
- The selected locale (of a hero) was not saved.
|
||||
- Pacts were not saved.
|
||||
- Wrong column names on chants sheet.
|
||||
- Protective/Wardings Circles are not correctly displayed.
|
||||
- Disadvantage *Incompetent* disallows Advantage *Aptitude* completely instead of only disallowing the corresponding skill option.
|
||||
- Primary Attribute maximum for Combat Techniques not working.
|
||||
- Weight Ratio of Head and Torso in Hit Zone Armor flipped.
|
||||
|
||||
## 1.1.0/[1.1.1](https://github.com/elyukai/optolith-client/compare/v1.0.3...v1.1.1)/[1.1.2](https://github.com/elyukai/optolith-client/compare/v1.1.1...v1.1.2) (2018-08-06)
|
||||
|
||||
*The exact version number of the app is 1.1.2.*
|
||||
|
||||
After more than one year a big update is finally coming—even though you probably won’t notice it: Optolith got a completely new code. This is accompanied by an internal restructuring, so that features like groups will be possible in the future. This is the biggest change, but not the only one: All reported bugs are fixed and over the last two weeks my testers found several bugs, which are also fixed here.
|
||||
|
||||
You will probably notice that Optolith takes a few seconds longer to load than before. This is because I rebuilt Optolith with the basic idea that it runs more stable. So I do checks during loading to make sure that all data that is loaded really looks as expected and so that nothing unexpected and unintentional, caused by wrong tables or other wrong data in the program, happens. The start performance is definitely going to be improved, but for now I prefer a more stable program to a fast one; after all this is only about starting the program, the performance during use is not affected.
|
||||
|
||||
Last but not least a big thank you to you, for your patience and all your feedback and further help, without which I would not have been able to fix all those bugs. Although new rules have not yet made it into this version, and the next version won’t contain all new ones, Optolith will be able to get up to date more quickly than before.
|
||||
|
||||
But now it’s time for the changelog of this version:
|
||||
|
||||
### New Features
|
||||
|
||||
- Heroes can now be saved from the hero list.
|
||||
- Avatars of heroes and pets can now be deleted.
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Select option lists are now filtered by activated books. Previously, that only worked for main lists like special abilities, but now it works for all list, for example the list of trade secrets.
|
||||
- Wiki entry lists cannot be filtered by specific books, neither can the wiki entry text in opened heroes. This will be implemented later!
|
||||
- Unfamiliar spells are now going to be highlighted in red (just like uncommon advantages).
|
||||
- Searching for professions now includes profession variants.
|
||||
- The space for languages on the character sheet is now twice as large.
|
||||
- When adding advantages or disadvantages, AP spent on advantages as well as on disadvantages will always be shown.
|
||||
- Shields on character sheet are now listed as a weapon as well as a shield.
|
||||
- Wiki texts are now selectable.
|
||||
- For *Tradition (Guild Mage)*, you need to set a specific unfamiliar spell. **For an already activated SA, you can to the via the *Rules* tab. Please set the spell, because it is needed for upcoming books!**
|
||||
- To be able to better handle differences between languages in terms of rules, heroes are now saving the languages they were created in. **For already created heroes, the creation language will be set to the current UI language. To change that, you need to switch to the *Rules* tab! Please check it, because it is going to be relevant in future versions!**
|
||||
- The term for generating random weight and size of the hero is now shown above the respective input field.
|
||||
- The dialog shown on application quit if there are unsaved actions is now clearer and shows three buttons for all three possible options.
|
||||
- Text on character sheet is now selectable. This option is not useful for copying the complete character sheet as the layout is not preserved properly.
|
||||
- Added FAQ entry on how to add LP/AE/KP.
|
||||
- If you open a wiki entry when a character is open, the respective list item is highlighted just like they are in the wiki.
|
||||
- The maximum available AP that can be left when finishing hero creation phase is now always 10 AP, no matter whether you are in editing mode or not.
|
||||
- Added *Uses* for skills.
|
||||
- For skills where it is not 100% clear whether encumbrance is relevant or not, the full description when it is relevant and when it is not is now shown instead of „maybe“.
|
||||
- The separate entries for advantage *Exceptional Sense* and disadvantage *Restricted Sense* are now joined.
|
||||
- *Additional sources* lists sources from which the entry gets its select options. For example, SA *Trade Secrets* not only shows the source for the base SA, but also the different trade secret sources from all the different books.
|
||||
- Analogous to the Raven’s Beaks blessed variant, there’s now a mundane variant available, too. It costs 90 S. There’s no difference between the two versions except for *blessed/mundane*.
|
||||
- For Staff Enchantments, *Binding of the Staff* as a prerequisite is now hidden (because it is hidden in Core Rules, too).
|
||||
- Finishing RCP selection can now be undone.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- After switching from German to English, heroes could not be edited anymore.
|
||||
- Special Abilities can now be sorted by group again.
|
||||
- Liturgical chant traditions and aspects are now sorted alphabetically.
|
||||
- Maxima of skills and combat techniques are now also influenced by skill check attributes or primary attributes during character creation phase.
|
||||
- Yellow-highlighted advantages/disadvantages are now explained in the legend on the left.
|
||||
- A spell or chant from a base profession removed by a profession variant now removes the entry entirely instead of activating it on SR 0.
|
||||
- Images are now shown on Linux again.
|
||||
- Automatic advantages cost 0 AP so that race AP cost are correct now.
|
||||
- Sorting equipment by group does not result in an error anymore.
|
||||
- Avatars of pets are now exported as well when exporting the character.
|
||||
- Details of spells and chants are shown on character sheet again.
|
||||
- The prerequisite Race, culture, or profession must have \[...] as an automatic or suggested \[advantage/disadvantage]“ now also includes automatic advantages if they are removed.
|
||||
- Pet layout on character sheet should work again.
|
||||
- The PDF does not include a scroll bar anymore.
|
||||
- Protective/Warding circles are now shown on character sheet.
|
||||
- Lowering attributes in editing mode takes added LP/AE/KP into account and restricts lowering if needed.
|
||||
- Sometimes, instead of a value, for example in *Enforce Honor 6*, `null` was shown in the wiki entry for professions, so it was actually *Ehrenhaftigkeit null*.
|
||||
- Sometimes, the value of a entry in a profession, changed by a profession variant, was increased by 6. For example, Knight: *Etiquette 2 instead of 12* actually is *Etiquette 2 instead of 6*.
|
||||
- Combat Style Special Abilities now show their type (passive/base maneuver/special maneuver) in wiki again.
|
||||
- Sorting spells by group now sorts the groups alphabetically.
|
||||
- When EL *Inexperienced* is selected and you got SA *Tradition (Guild Mage)*, you can now also buy and increase the selected unfamiliar spell (which is not unfamiliar anymore).
|
||||
- The select option list for SA *Tradition (Guild Mage)* does not list familiar spells anymore.
|
||||
- Disadvantage *Negative Trait (Stinginess)* and *(Wastefulness)* were not mutually exclusive.
|
||||
- Spells and chants are not included in disadvantage *Incompetent*. Heroes with those entries will get the disadvantage removed automatically.
|
||||
- SA *Aspect Knowledge* can now be added as expected.
|
||||
- SA *Property Knowledge* can now be added as expected.
|
||||
- SA *Precise Shot/Throw* had wrong attribute prerequisites.
|
||||
- Disadvantages *No Flying Balm* and *No Familiar* now correctly decrease the cost of SA *Tradition (Witch)* by 10 AP each.
|
||||
- SA *Tradition (Guild Mage)* is now taken into account for restrictions concerning unfamiliar spells (see the change to the SA listed above also).
|
||||
- *Disguise*, Encumbrance: Maybe → Yes.
|
||||
- *Etiquette*, Encumbrance: Maybe → No.
|
||||
- *Persuasion*: Skill check COU/INT/CHA → COU/SGC/CHA.
|
||||
- Wrong skill checks of spells:
|
||||
- *Magical Melody*: COU/SGC/CHA → COU/INT/CHA
|
||||
- *Song of Piece*: COU/SGC/INT → COU/INT/CHA
|
||||
- *Song of Sorrow*: COU/CHA/CHA → COU/INT/INT
|
||||
- Text missing which states that a special aspect of a spell cannot be modified:
|
||||
- MOTORICUS: Cost
|
||||
- WALL OF FOG: Cost
|
||||
- Missing skill check modifiers (SPI/TOU) added to spells.
|
||||
- ANALYZE ARCANE STRUCTURE: Casting time 2 actions → 32 actions.
|
||||
- KLARUM PURUM, Cost: 8 AE.
|
||||
- Wrong skill checks of liturgical chants:
|
||||
- *Minor Banishing Ray*: COU/SGC/CHA → COU/INT/CHA
|
||||
- *Enforce Honor*: COU/SGC/CHA → COU/INT/CHA
|
||||
- *Encourage*: COU/SGC/CHA → COU/INT/CHA
|
||||
- *Peaceful Aura*: COU/SGC/CHA → COU/INT/CHA
|
||||
- *Hand mirror* now lists the corresponding rule.
|
||||
- *Dog food, Horse feed, Pony feed*: Removed weight.
|
||||
- *Rope, climbing* now has a correct price.
|
||||
|
||||
## [1.0.3](https://github.com/elyukai/optolith-client/compare/v1.0.2...v1.0.3) (2018-05-05)
|
||||
|
||||
- Renamed Optolyth to Optolith to be in line with all other locations where the name occurs.
|
||||
|
||||
### Linux
|
||||
|
||||
- Fixed glibc-related issue.
|
||||
- Moved from tar.gz to AppImage to provide auto-update support.
|
||||
|
||||
## [1.0.2](https://github.com/elyukai/optolith-client/compare/v1.0.1...v1.0.2) (2018-04-16)
|
||||
|
||||
- Fixed issue with rendering list items of advantages/disadvantages/special abilities.
|
||||
- Fixed shortcuts being global.
|
||||
- Reloading a hero with `Stigma (Albino)` won’t change selected eye color anymore.
|
||||
|
||||
### macOS
|
||||
|
||||
- App now quits automatically on close.
|
||||
|
||||
### English Specific
|
||||
|
||||
- Fixed list of professions.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed typo in failed check description of skill "Driving".
|
||||
- Fixed attribute adjustment in attributes tab.
|
||||
- Fixed levels of required instances of `Principles` and `Obligations` of `Blessed One of Kor`.
|
||||
|
||||
## [1.0.1](https://github.com/elyukai/optolith-client/compare/v1.0.0...v1.0.1) (2018-04-08)
|
||||
|
||||
- Fixed special abilities tab.
|
||||
|
||||
## [1.0.0](https://github.com/elyukai/optolith-client/compare/v0.51.9...v1.0.0) (2018-04-08)
|
||||
|
||||
### New Features / Breaking Changes
|
||||
|
||||
- New app name: Optolyth (long version: Optolyth Character Generator)
|
||||
- Linux 32-bit build available.
|
||||
- Applications that can be purchased using advantages or special abilities can be used for a skill specialization. They are listed in the wiki below *New Applications*, too.
|
||||
|
||||
### Rules/Crunch
|
||||
|
||||
- Language Specializations can be activated as an Optional Rule in the Rules tab.
|
||||
- Added missing entries for *Immunity to [poison/disease]* from **Core Rules** and **Aventuria Almanach**.
|
||||
|
||||
### Bug Fixes / Other
|
||||
|
||||
- Multiline texts are now multiline on character sheet.
|
||||
- If advantage *Stigma (Albino)* is purchased the (lists of available) hair and eye colors change.
|
||||
- AP cost for race are now shown in AP tooltip. AP cost for profession are shown only when RCP selection has not been approved.
|
||||
- Fixed converting older heroes to new version.
|
||||
- The text fields for size and weight are both updated on generating size or weight.
|
||||
- In profile overview now always shows the start EL. Before, the EL was calculated based on AP total.
|
||||
- Now you can select the source books to use in the very beginning of character creation.
|
||||
- Fixed prerequisites of *Cartography*.
|
||||
- Fixed list of liturgical chants.
|
||||
- Ignoring changes on close now closes the app. Before nothing happened ...
|
||||
- Fixed calculation of minimum/maximum liturgical chant SR.
|
||||
- Shortcuts!
|
||||
- `Ctrl+Z` => Undo
|
||||
- `Ctrl+Y` or `Ctrl+Shift+Z` => Redo
|
||||
- `Ctrl+O` => Show settings (only when dedicated button is available, too)
|
||||
- `Ctrl+S` => Save hero
|
||||
- `Ctrl+W` => Switches to list of heroes if a hero is currently opened (similar to the back button in the top left corner)
|
||||
- `Cmd+Q` => Close app (macOS)
|
||||
- Abbreviations for related source books are shown in the list of professions again.
|
||||
- Fixed importing heroes with images encoded in `jpg`.
|
||||
- Fixed editing automatic Area Knowledge in profile overview.
|
||||
- Fixed calculation total AP for disadvantage *Bad Habit*.
|
||||
- The *About ...* menu element is disabled if there is an open dialog window (macOS).
|
||||
- The AP cost for profession variants are now considered when calculating AP spent.
|
||||
- Activating spells is now disabled the limits of EL are reached.
|
||||
- Fixed showing separators when sorting spells or liturgical chants by group.
|
||||
- Fixed text fields in animal section of the character sheet breaking layout or showing up twice.
|
||||
- Fixed AP used for dis/advantages shown in window used for adding them. Remaining parts of the old AP system have been removed.
|
||||
- Fixed saving heroes (previously saved heroes are not currupted).
|
||||
- Fixed filters for list of races and list of professions.
|
||||
- Fixed calculating AP total for *Obligations/Priciples*.
|
||||
- Fixed showing damage info for weapons in wiki (if a weapon has no flat damage 1W6 instead of 1W60 is shown now).
|
||||
|
||||
### German Specific (in German)
|
||||
|
||||
#### New Features / Breaking Changes
|
||||
|
||||
- Unter Profil > Pakt kann nun ein Feenpakt eingetragen werden. Die Eingabe richtet sich genau nach den Regeln aus **Die Siebenwindküste** zu Feenpakten. Die Eingabe dort schaltet auch unter erfüllten Voraussetzungen Paktgeschenke frei.
|
||||
|
||||
#### Rules/Crunch
|
||||
|
||||
- Professionen aus **Aventurisches Götterwirken** und **Die Siebenwindküste** hinzugefügt.
|
||||
- Fehlende Berufsgeheimnisse aus **Rüstkammer der Streitenden Königreiche** und **Rüstkammer der Siebendwindküste** sowie **Aventurisches Bestiarium** hinzugefügt.
|
||||
- Fehlende Einträge für *Immunität gegen [Gift/Krankheit]* aus **Aventurisches Bestiarium**, **Aventurische Magie I** und **Kneipen & Tavernen** hinzugefügt.
|
||||
|
||||
#### Bug Fixes / Other
|
||||
|
||||
- Die Profession *Sangara* funktioniert wieder ordnungsgemäß.
|
||||
- Die AP-Kosten für *Tradition (Zauberbarde)* und *Tradition (Zaubertänzer)* werden beim Hinzufügen wieder korrekt angezeigt.
|
||||
- Liturgiestilsonderfertigkeiten, die es ermöglichen, Liturgien anderer Gottheiten zu erlernen, ermöglichen dieses Feature nun auch.
|
||||
- Die Tradition des Namenlosen Kultes wird im Wiki nun korrekt angezeigt.
|
||||
- Die Abhängigkeiten der Beschwörungs/Herbeirufungszauber werden nun in den FAQ behandelt.
|
||||
- Die Berechnung der Wundschwelle funktioniert wieder ordnungsgemäß.
|
||||
- Stilsonderfertigkeiten und dazugehörige Erweiterte SF können nun wieder ordnungsgemäß hinzugefügt und gelöscht werden.
|
||||
- Die Wundschwelle wird nun nur angezeit, wenn **Aventurisches Kompendium** verfügbar und aktiviert ist.
|
||||
- Die Reichweite für Waffen der Streitenden Königreiche wurde ergänzt.
|
||||
- Vor- und Nachteile für *Intuitive Zauberer* können wieder ordnungsgemäß erworben und verkauft werden.
|
||||
- Bei einigen Zaubern wurde die druidische oder scharlatanische Tradition ergänzt.
|
||||
|
||||
## [0.51.9](https://github.com/elyukai/optolith-client/compare/v0.51.8...v0.51.9) (2017-12-25)
|
||||
|
||||
- The AP tooltip (if you hover over the available AP of the currently opened character) now show how many AP you spent on which category of entries - how much you spent on spells, on special abilities, on disadvantages, on cantrips, on energies (LP, AE, KP) and so on.
|
||||
- I build a new AP system which dynamically calculates the AP. This means if there were or will be issues with calculated AP they will be fixed automatically by installing the patch that fixes this issue. (You still have to open the character and save it, though).
|
||||
- Fixed AP value calculation of *Skill Specialization (Skill)*.
|
||||
- Lists belonging to a hero (active special abilities, inactive special abilities, skills, professions and so on) now show a placeholder element when the respective list is empty.
|
||||
- Slightly improved performance.
|
||||
- Improved displaying numbers in some areas.
|
||||
- You can now activate or deactivate if you want to see animations or not throught the settings window.
|
||||
- Wiki lists now display levels if available (example: `Rich I-X` instead of `Rich`).
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed prerequisites of *Abrollen*.
|
||||
- Blessings are now tradition-aware: If you select a tradition of a demigod you will not be able to select the unavailable blessings (as specified by tradition special ability) anymore.
|
||||
|
||||
### English Specific
|
||||
|
||||
- There are full wiki entries available for *all* currently implemented entries.
|
||||
|
||||
## [0.51.8](https://github.com/elyukai/optolith-client/compare/v0.51.7...v0.51.8) (2017-12-19)
|
||||
|
||||
- Fixed AP cost for languages.
|
||||
- Fixed generating random values for size and weight of a character.
|
||||
- Added error binding to content, so that if an error occurs you can still access the navigation bar.
|
||||
- Fixed crashes of wiki box in English version.
|
||||
|
||||
## [0.51.7](https://github.com/elyukai/optolith-client/compare/v0.51.6...v0.51.7) (2017-12-18)
|
||||
|
||||
- Tweaked some colors to improve UI readability.
|
||||
- Error messages (the ones that contain "error" and "component stack") can be selected and thus copied.
|
||||
- After creating a hero (temporary name, sex, experience level) you will be in the "rules" tab, which is a main tab in this phase of character creation. You can select race, culture and profession by switching to the respective tab.
|
||||
- You can now check for updates manually via the Settings window.
|
||||
- The wiki, which is shown if you switch to the "wiki" tab, is now complete - regarding functionality. Contents still vary depending on the selected language.
|
||||
- If race Elf is selected the app will no longer warn you to not use the selected profession because the calculated AP would be incorrect.
|
||||
- *Cendrash Style* is fixed and can be added to your character(s) again.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Zone armor tab will only be shown if *Aventuria Armory* or *All rule books* are activated in "rules" tab.
|
||||
- ENC is now correctly calculated for zone armor.
|
||||
- Fixed *Brevier der zwölfgöttlichen Unterweisung*.
|
||||
- Fixed magical styles.
|
||||
|
||||
### English Specific
|
||||
|
||||
- App will no longer crash all the time.
|
||||
|
||||
## [0.51.6](https://github.com/elyukai/optolith-client/compare/v0.51.5...v0.51.6) (2017-12-11)
|
||||
|
||||
- Added sort option to inventory to sort by weight (in descending order).
|
||||
- *Slow* is now considered when calculating MOV value.
|
||||
- Fixed magical and blessed professions.
|
||||
|
||||
## [0.51.5](https://github.com/elyukai/optolith-client/compare/v0.51.4...v0.51.5) (2017-12-11)
|
||||
|
||||
- Fixed hidden liturgical chants tab.
|
||||
|
||||
## [0.51.4](https://github.com/elyukai/optolith-client/compare/v0.51.3...v0.51.4) (2017-12-09)
|
||||
|
||||
- Fixed MOV modificators.
|
||||
- Splitted tradition special abilties to be able to provide wiki texts for them.
|
||||
- Fixed some profession and profession variant values.
|
||||
- Moved item infos to the wiki box on the right; removed the tooltip shown on hover before.
|
||||
- Fixed resetting AP on changing race variant.
|
||||
- Selection lists for disadvantages *Principles* and *Obligations* are not filtered by selected level anymore. This means, you can select *Code of the Church of Peraine* for all three levels of *Principles* instead of typing in the optional input field.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Added **ALL** missing texts to complete the offline wiki.
|
||||
- Joined the different `Bindung des Bannschwerts` special abilties.
|
||||
- Additional item texts are only shown for templates and locked entries.
|
||||
- Added missing armory from regional armory books.
|
||||
|
||||
### English/Dutch Specific
|
||||
|
||||
- Wiki boxes will show the entries that can be generated safely. Their full functionality will be enabled once the needed texts are added.
|
||||
|
||||
## [0.51.3](https://github.com/elyukai/optolith-client/compare/v0.51.2...v0.51.3) (2017-12-01)
|
||||
|
||||
- Fixed issues with prerequisites for races.
|
||||
- Fixed profession list filter.
|
||||
- Fixed showing levels multiple times on character sheet and in profile overview.
|
||||
- Ignores auto updater on Linux.
|
||||
- Small tweaks to the light theme.
|
||||
- Some fixes for items regarding price and weight.
|
||||
- There are now additional infos about an update available in the download progress dialog.
|
||||
- The Enter key should work as usual after closing the app.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Added filtering by activated rule books and wiki entries for combat techniques, advantages, disadvantages and special abilities. Prerequisite texts for advantages, disadvantages and special abilities are missing, though.
|
||||
- Removed special ability *Magischer Gedankenschutz* as this the exact same special ability as *Gedankenschutz*, the only difference is the name and this seems to be a issue with consistent naming.
|
||||
|
||||
## [0.51.2](https://github.com/elyukai/optolith-client/compare/v0.51.1...v0.51.2) (2017-11-21)
|
||||
|
||||
- Fixed calculating AP for *activated* special ability entries where you have to buy each tier separately (e.g. *Feint I-III*). Previously, only the cost for the highest bought tier were shown.
|
||||
- Removing bought energy points now correctly lowers AP spent. Previously, it did not change the current AP spent.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed *Lederüstung* typo.
|
||||
|
||||
## [0.51.1](https://github.com/elyukai/optolith-client/compare/v0.51.0...v0.51.1) (2017-11-19)
|
||||
|
||||
- Fixed Auto Updater.
|
||||
- Added FAQ page.
|
||||
- It is now possible to change attribute adjustment selection from selected race during character creation.
|
||||
- Fixed shown special ability groups on character sheet.
|
||||
- Fixed displayed aspects of liturgical chants.
|
||||
- Fixed calculating PA on character sheet.
|
||||
- You can now select the rule books you want to include in the Profile > Rules tab. Inactive book entries will not be displayed (only Races, Cultures, Professions, Skills, Spells and Liturgical Chants).
|
||||
- Displaying races now corresponds with the Core Rules.
|
||||
- Fixed inactive Advantages/Disadvantages/Special Abilities list’s layout (temporarily).
|
||||
|
||||
### macOS
|
||||
|
||||
- Added custom titlebar.
|
||||
- Added *About ...* app menu option.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed *Visions* and *Sermons* for Blessed Ones.
|
||||
- Fixed profession *Graumagier (Schule der Verformungen zu Lowangen)*.
|
||||
|
||||
## [0.51.0](https://github.com/elyukai/optolith-client/compare/v0.50.0...v0.51.0) (2017-11-14)
|
||||
|
||||
- Auto-Updater for Windows and macOS.
|
||||
- Installer for Windows.
|
||||
- New icon set.
|
||||
- You can now edit heroes after character creation phase if you check the respective box in the settings.
|
||||
- Enabled filtering of item templates by combat technique.
|
||||
- Exported characters will now include the avatar image.
|
||||
- Added support for permanent LP loss.
|
||||
- Source book abbreviations will now appear in profession list.
|
||||
- All lists of skills (skills, spells, liturgical chants) will now show the attribute values if you hover over an list item.
|
||||
- Fixed calculations of AP spent on **active** advantages/disadvantages/special abiltiies (AP for buying them were still calculated correctly).
|
||||
- Fixed calculations based on size value (meter-based size entry allowed).
|
||||
- Fixed surplus page in character sheet PDF.
|
||||
- Added placeholder wiki boxes for advantages, disadvantages, special abilities and items.
|
||||
- Fixed available hair colors for some races.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Added spell and liturgical chant extensions to wiki boxes.
|
||||
- Fixed text of *magical professions* for culture *Ore Dwarf*.
|
||||
|
||||
### Dutch Specific
|
||||
|
||||
- Started localization work.
|
||||
|
||||
## [0.50.0](https://github.com/elyukai/optolith-client/compare/v0.49.6...v0.50.0) (2017-11-02)
|
||||
|
||||
- Custom AP cost possible for advantages and disadvantages.
|
||||
- Fixed ItemEditor’s and ArmorZonesEditor’s theming and layout.
|
||||
- Slightly brightened the color for disabled elements.
|
||||
|
||||
## [0.49.6](https://github.com/elyukai/optolith-client/compare/v0.49.5...v0.49.6) (2017-10-23)
|
||||
|
||||
- Fixed dis/advantages from **Aventurian Magic I**.
|
||||
- Fixed alerts after selecting RCP.
|
||||
- Alerts might be a bit more responsive (internal rework).
|
||||
- Fixed *Last Changes* page.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Added wiki info for cultures.
|
||||
|
||||
## [0.49.5](https://github.com/elyukai/optolith-client/compare/v0.49.4...v0.49.5) (2017-10-15)
|
||||
|
||||
- Special abilities with tiers do not have separate entries anymore.
|
||||
- RCP costs are now entirely calculated by the app.
|
||||
- Changed ItemEditor layout from one-column to two-column layout.
|
||||
- Extended P+T functionality and fixed items based on that.
|
||||
- Now allows negative AP left during character creation phase.
|
||||
- Fixed search fields (it unintentionally converted text into a regular expression - thus some strange results).
|
||||
- Fixed filtering magical tradition’s selection objects (previously, having more than 25 AP spent/received for magical advantages/disadvantages adding a tradition was completely disabled).
|
||||
- Fixed professions requiring specific cultures.
|
||||
- Fixed *Exceptional Skill/Combat Technique*
|
||||
- Fixed calculating TOU.
|
||||
- Fixed showing Own Profession without having extension books in profession list.
|
||||
- Added prerquisites and special abilities entries for professions’ wiki info.
|
||||
- Fixed disabling to add AE instead of to add permanently lost AE when more than one permanent AE point is lost.
|
||||
- Wiki test page added.
|
||||
- Added *Last Changes* tab in *About* section.
|
||||
- Some further redesign work.
|
||||
- A short message is now shown in Settings dialog informing about you need to restart the app when changing the language.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Added (missing) wiki info for skills, cantrips, blessings, spells and liturgical chants (excluding extensions).
|
||||
- Added special abilities, liturgical chant extensions and dis-/advantages from **Aventurian Work of the Gods**.
|
||||
|
||||
## [0.49.4](https://github.com/elyukai/optolith-client/compare/v0.49.3...v0.49.4) (2017-09-10)
|
||||
|
||||
- Fixed character portrait selection.
|
||||
- Heaps of stuff for the new design.
|
||||
- Added theme selection to Settings modal (although the light theme is not finished yet; modals and tooltips are still dark ...).
|
||||
- Deleting a character must be confirmed now (and cannot be undone after that).
|
||||
- Now shrinks text in specific fields on character sheet.
|
||||
- Fixed routine check modifiers on character sheet.
|
||||
- Unified RCP views.
|
||||
- Added RCP info views, although a lot of text is still missing.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed newer items.
|
||||
- Fixed combat technique values of newer professions.
|
||||
|
||||
## [0.49.3](https://github.com/elyukai/optolith-client/compare/v0.49.2...v0.49.3) (2017-09-04)
|
||||
|
||||
- Fixed AP limits for advantages/disadvantages.
|
||||
- Removes existing equipment from item templates search results.
|
||||
- New icon.
|
||||
- New background.
|
||||
- Updated titlebar icons on Windows and Linux.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed Skills selection for *Meistertalentierte*.
|
||||
|
||||
### English Specific
|
||||
|
||||
- Fixed RCP view issue(s).
|
||||
- Fixed Equipment view.
|
||||
|
||||
## [0.49.2](https://github.com/elyukai/optolith-client/compare/v0.49.1...v0.49.2) (2017-08-25)
|
||||
|
||||
- Fixed the addition of adventure points.
|
||||
- Fixed second combat technique selection for professions.
|
||||
- Fixed Settings modal.
|
||||
- Fixed small issue concerning `alert`s.
|
||||
- Item templates are no longer cleared after a new hero has been created.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Fixed problems with displaying AE for *Intuitiver Zauberer*.
|
||||
|
||||
### English Specific
|
||||
|
||||
- Fixed race/professions list.
|
||||
|
||||
## [0.49.1](https://github.com/elyukai/optolith-client/compare/v0.49.0...v0.49.1) (2017-08-14)
|
||||
|
||||
- Fixed issue with lists for advantages, disadvantages and special abilities containing skills/spells/liturgical chants.
|
||||
- Fixed issue with RCP skill specialization selection.
|
||||
- Fixed exporting heroes as JSON.
|
||||
- Fixed equipment view.
|
||||
- Professions are now sorted by name and them by subname (e.g. the academy name). Previously, professions with subnames were sorted randomly (changing from render to render).
|
||||
|
||||
### English Specific
|
||||
|
||||
- Added translation for skill name separation in RCP skill specialization selection.
|
||||
|
||||
## [0.49.0](https://github.com/elyukai/optolith-client/compare/v0.48.1...v0.49.0) (2017-08-08)
|
||||
|
||||
- *Smash* does not require *Rundumschlag I* anymore.
|
||||
- Protective/Warding Circles now require *Magical Signs*.
|
||||
- Added missing *Ancestor Glyphs* from The Warring Kingdoms. They require *Magical Signs* as well.
|
||||
- *Nimble* now increases MOV by 1.
|
||||
- *Immunity to (Disease/Poison)* now correctly adds the selected entry.
|
||||
- Derived characteristics are now correctly calculated on character sheet.
|
||||
- Item templates have floating number values (again). This **won’t** affect non-locked item templates in items.
|
||||
- Requiring Spells now correctly disables the decrease button if the SR hits the required value.
|
||||
- Invalid avatar paths will no longer result in a colored border with black background and an invalid file error in the console.
|
||||
- Now filters Aspect Knowledge selection by active tradition.
|
||||
- Now sorts lists properly depending on the selected locale (e.g. ä, ö and ü in German are basically treated as a, o and u respectively).
|
||||
- Items now correcly reset if you load a character after you opened/created a character with items.
|
||||
- Item weights are now correctly displayed.
|
||||
- Item PA mods do not use the AT mod value anymore.
|
||||
- *Improved Dodge I-III* now increases DO value.
|
||||
- Profession *Spy* now correctly increases *Commerce* by 3.
|
||||
- The used attributes for the skill check of ODEM ARCANUM are SGC/INT/INT now (following the German Regel-Wiki).
|
||||
- *Combat Reflexes I-III* now increases INI value.
|
||||
- Window is maximizable, unmaximizable and resizable.
|
||||
- Added a title bar on Windows and Linux, providing buttons in Windows 10 UWP style to minimize, (un)maximize and close the app. This bar is also the draggable area of the window.
|
||||
- Removed the Start tab.
|
||||
- Improved general performance.
|
||||
- In *Profile Overview* and *Character Sheet* tabs, entries such as `Skill Specialization (Climbing: Trees), Skill Specialization (Survival: Find Campsite)` are now written as `Skill Specialization (Climbing: Trees, Survival: Find Campsite)`, providing a better overview and readability in addition to more space.
|
||||
- A new Redo button is added to the Navigation Bar. History resets after saving, finishing RCP selection and finishing character creation as well as switching to another character.
|
||||
- Fixed *Property Knowledge*’s and *Aspect Knowledge*’s AP cost and effects.
|
||||
- Fixed Languages and Scripts selection texts in RCP selections window.
|
||||
- Fixed loading characters with active Blessings.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Includes new traditions, aspects and liturgcal chants from **Aventurisches Götterwirken I**.
|
||||
- Added info for selected race in Races tab.
|
||||
- CON instead of COU now increases WS value.
|
||||
- Fixed *Intuitive Caster*’s spell limit and AE value.
|
||||
|
||||
### English Specific
|
||||
|
||||
- Added info box for selected race in Races tab, but (most of the) texts are still missing.
|
||||
|
||||
## [0.48.1](https://github.com/elyukai/optolith-client/compare/v0.48.0...v0.48.1) (2017-06-13)
|
||||
|
||||
- Undo function is working again for advantages, disadvantages and special abilities.
|
||||
- Spells can now be de/activated as intended.
|
||||
- Cantrips/Blessing are now sorted on the character sheet.
|
||||
- The total attribute maximum is shown on the attribute page during character creation.
|
||||
|
||||
## [0.48.0](https://github.com/elyukai/optolith-client/compare/v0.47.3...v0.48.0)
|
||||
|
||||
- Heaps of bugs fixed. I lost sight of all bugs, so I won’t provide a detailed list this time.
|
||||
- Added a funtion to duplicate heroes.
|
||||
- New interface for managing permanent AE/KP loss.
|
||||
- Windows x86 support. (I am sorry for all the macOS users out there; I am still searching for a good way to build for Mac as I do not have a Mac at home.)
|
||||
- Removed support for heroes created with app version lower than 0.45.0.
|
||||
- If advantage/disadvantage list is empty, a placeholder will be shown. This is to ensure that having an empty list is intentional - and not a bug.
|
||||
|
||||
### German Specific
|
||||
|
||||
- Aventurian Names, Inns & Taverns, Aventurian Bestiary and The Warring Kingdoms are fully implemented (they should be). All entries from Aventurian Magic I are available. Optional Rules from AMI are not (yet) available.
|
||||
- AMI is now basically complete. During reworking the entries some issues occured that might cause your characters to not work properly.
|
||||
- Selecting *Meistertalentierte*, *Zauberbarden* or *Zaubertänzer* as your magical tradition requires a second selection. *Meistertalentierte* choose their *Meistertalent*, *Zauberbarden* or *Zaubertänzer* choose their music/dance tradition (Ceoladir etc). If the affected characters are in character creation phase, you will be able to remove the existing tradition special ability and reactivate it with the correct selection. If character creation is finished, you have to create the respective character(s) again.
|
||||
- *Exorzist I* and *Meistertrick I* were missing selection options. Now they are included. The selection options do not affect the AP cost, so you can try to fix it the same way as you did for the magical tradition above.
|
||||
|
||||
## [0.47.3](https://github.com/elyukai/optolith-client/compare/v0.47.2...v0.47.3)
|
||||
|
||||
- The requirement *Manifesto 10* for Elemental Servant referred to Wall of Fog instead of Manifesto.
|
||||
|
||||
## [0.47.2](https://github.com/elyukai/optolith-client/compare/v0.47.1...v0.47.2) (2017-05-24)
|
||||
|
||||
- The calculations of total weight and total price in equipment tab should work again. Armor is excluded from the total weight calculation, because armor is excluded from carrying capacity if its in use.
|
||||
- Character Sheet is working again.
|
||||
- The skills tab contains a pane on the right side. If you click on the "i" buttons, it will show you additional information about the respective skill. Well, it will show you in the future: Currently, there is not much data available to show.
|
||||
- The item list now properly resets for every new character.
|
||||
|
||||
## [0.47.1](https://github.com/elyukai/optolith-client/compare/v0.47.0...v0.47.1) (2017-05-18)
|
||||
|
||||
- Fixed an issue with a race or a culture as a requirement. This issue caused some professions to never appear in the list.
|
||||
|
||||
## 0.47.0 (2017-05-15)
|
||||
|
||||
- Die englische Übersetzung ist nun vollständig. (Jedenfalls hoffe ich, dass ich nichts übersehen habe! ^^)
|
||||
- Es gibt den Eintrag "Körpergröße" im Profil nun nicht mehr zweimal.
|
||||
- Die Kosten für SF Lieblingszauber sollten wieder passen.
|
||||
- Berechnung von Körpergewicht ohne das Vorhandensein der Körpergröße gibt nun nicht mehr NaN aus, sondern berechnet beide Werte entsprechend neu.
|
||||
- Die Buttons zur Zufallsgenerierung auf der Profilseite haben nun ein Würfelsymbol.
|
||||
- Beim Eintragen von neuen AP nach der Charaktererstellung wird nun überprüft, ob man nur Zahlen eingibt. Ist es nicht der Fall, weil das Textfeld z.B. Text oder Kommata enthält, kann man den Wert nicht hinzufügen.
|
||||
- Im Gegenstandseditor wird einem nun angezeigt, wenn ein Feld zur Speicherung gefüllt sein muss oder zur Speicherung keinen kompatiblen Inhalt hat. Was kompatibel ist, sollte jedem eigentlich klar sein (kein Text beim AT-Modifikator etc.), daher habe ich mir die Erläuterungen da gespart.
|
||||
- Merkmalsfokus und Varianten funktionieren jetzt aber wirklich.
|
||||
- Der Charakterbogen enthält nun auch endlich den Begleiter als Eintrag.
|
||||
|
||||
## 0.46.0 (2017-05-02)
|
||||
|
||||
- Ein Fehler bei den Profession wurde behoben, durch den diese falsch gefiltert wurden.
|
||||
- Der Tooltip für AP funktioniert jetzt wieder.
|
||||
- In der Hinzufügen-Liste für Nachteile bleiben jetzt auch die Nachteile; seit dem letzten Patch wurden nach jeder UI-Aktualisierung Vorteile statt Nachteile geladen.
|
||||
- Das Kulturpaket ist jetzt standardmäßg deaktiviert, die Kosten stehen bei der Kultur aber immer noch bei. Aktivieren kann man es dann - wie gewohnt - im SKP-Optionsfenster.
|
||||
- In den Auswahlfenstern für Vor- und Nachteile werden auch die entsprechenden Grenzen für Gesamt-AP, AP für magische Vor-/Nachteile und AP für karmale Vor-/Nachteile angezeigt.
|
||||
- Talente, Zauber und Liturgien werden bei einer Gruppensortierung visuell voneinander getrennt.
|
||||
- Die Sortierfunktion nach Gruppe funktioniert bei Zaubern und Liturgien jetzt wieder.
|
||||
- Man kann nun Zonenrüstung erstellen.
|
||||
- Man kann sich nun einen Begleiter erstellen. Die Integration in den Heldenbogen kommt dazu sehr bald!
|
||||
- Einträge aus AMI sind in der PREVIEW enthalten (war auch schon letztes Mal so), d.h. sie enthalten die angegebenen Voraussetzungen, jedoch keine Auswirkungen.
|
||||
- Ausgewählte Einträge für SF Variante und SF Merkmalsfokus werden jetzt auch gespeichert.
|
||||
- Zaubertricks funktionieren jetzt wieder so wie sie sollen.
|
||||
- Ich habe mit einer Übersetzung der UI für den nicht-deutschsprachigen Teil der Community begonnen. Die Sprache wird anhand eures Systems bestimmt und daher auf Deutsch oder Englisch (noch Deutsch+Englisch ^^) eingestellt. Ihr könnt die Sprache festlegen, indem ihr außerhalb der Heldenerstellung oben rechts die Einstellungen öffnet und dann die Sprache auswählt.
|
||||
+23
-7
@@ -32,12 +32,13 @@ Due to licensing, the YAML files containing the crunch elements from the books a
|
||||
|
||||
### Basic development rules
|
||||
|
||||
- Branches (mostly) follow Git Flow. Please try to follow Git Flow as well if you edit this repository. But if it's possible, please fork this repo and create PRs.
|
||||
- Branches follow Git Flow. Please try to follow Git Flow as well if you edit this repository. But if it's possible, please fork this repo and create PRs.
|
||||
- Commits need to follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), which is enforced by the CI and Git Hooks locally.
|
||||
- Please check out the [Style Guide](https://github.com/elyukai/optolith-client/wiki/Code-Style-Guide) for more information on that topic! Sadly, TSLint cannot ensure the whole style so you partially need to take care of that on your own!
|
||||
- There is a page covering [Naming Conventions](https://github.com/elyukai/optolith-client/wiki/Naming-Conventions) as well!
|
||||
- The key criteria for the project are mentioned in the [README](README.md). They also apply to pull requests. A feature usually does not consist of the technical part only. Much more important is how the feature presents itself to the user. And that must be planned and discussed.
|
||||
- Code should follow functional programming practices while maintaining readablilty – which can be an issue using JavaScript/TypeScript, which is why in certain circumstances imperative/OOP code is allowed. It is also possible to write Reason/OCaml code, which compiles to JS.
|
||||
- The contribution has to be easy to understand not only for players using the app, but also for coders trying to further enhance the app: Please document at least module exports!
|
||||
- The contribution has to be easy to understand not only for players using the app, but also for coders trying to further enhance the app: Please document *at least* module exports!
|
||||
|
||||
### Prepare the repo
|
||||
|
||||
@@ -57,19 +58,34 @@ This installs all necessary packages.
|
||||
|
||||
### First run
|
||||
|
||||
Compile the source code. `ts:` is for TypeScript, `re:` for Reason/OCaml.
|
||||
Compile the source code. `js:` is for TypeScript/Webpack, `re:` for Reason/OCaml and `css:` for CSS/Sass. Webpack does not check the TypeScript validity, to check this, use `ts:` commands.
|
||||
|
||||
You have to run the Reason build first as it generates TypeScript files.
|
||||
You have to run the Reason build first as it generates TypeScript files...
|
||||
|
||||
```sh
|
||||
npm run re:build
|
||||
npm run ts:build
|
||||
npm run css:build
|
||||
npm run js:build
|
||||
```
|
||||
|
||||
…or watch it for better performance for subsequent compiling on save.
|
||||
(There's also a shorthand for that:)
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
…or watch it for better performance for subsequent compiling on save (you need to use separate terminal instances then).
|
||||
|
||||
```sh
|
||||
npm run re:watch
|
||||
npm run css:watch
|
||||
npm run js:watch
|
||||
```
|
||||
|
||||
To check the TypeScript validity, either check once or watch for changes (I'd always recommend to do the latter one).
|
||||
|
||||
```sh
|
||||
npm run ts:build
|
||||
npm run ts:watch
|
||||
```
|
||||
|
||||
@@ -89,7 +105,7 @@ npm run css:build
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
Clean the files built by the Reason compiler.
|
||||
Clean the files built by the Reason compiler, if there are any issue with it.
|
||||
|
||||
```sh
|
||||
npm run re:clean
|
||||
|
||||
-1
Submodule app/Database deleted from 56da58a2cf
@@ -1,73 +0,0 @@
|
||||
## 1.4.0
|
||||
|
||||
Please checkout the characters for which you've added SA *intuition tactique* or SA *détection des embuscades*. You'll notice it does not have it's previous name. This is due to an error (mentioned below) where the names of those two special abilities were swapped. The internal representation cannot change so the entries have to be "renamed". This should also fix the "incorrect" prerequisites of both entries.
|
||||
|
||||
### Features
|
||||
|
||||
- Italian translation [#667](https://github.com/elyukai/optolith-client/issues/667) [#610](https://github.com/elyukai/optolith-client/issues/610)
|
||||
- Optional paper-style background for character sheet. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Character Sheet view zoom option. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Add/Subtract money from purse. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Weight is now shown in an own column in equipment tab. [#305](https://github.com/elyukai/optolith-client/issues/305)
|
||||
- Unnecessary combat sheets are not used anymore, it now depends on which armor the character has. It defaults to the normal armor if no armor is present. [#407](https://github.com/elyukai/optolith-client/issues/407)
|
||||
- You can now click on entry names as well to show it's rules text. [#556](https://github.com/elyukai/optolith-client/issues/556)
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- Variants of profession *Blessed One of Boron* had wrong skill modifications. [#548](https://github.com/elyukai/optolith-client/issues/548)
|
||||
- Profession *White Mage (Sword & Staff)* had a wrong AP value and an additional skill modification. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Fixed common professions of a culture will now be shown even if they are not from the **Core Rules**. [#563](https://github.com/elyukai/optolith-client/issues/563)
|
||||
- The interface could be scaled down. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Weapons of the combat technique *Lances* were not shown on character sheet. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Attributes ignored the minimum caused by high skill ratings. The maximum of skill rating is the highest linked attribute + 2, so the highest attribute is required to have a minimum value of SR − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- The names for SA *intuition tactique* and SA *détection des embuscades* were swapped. [#636](https://github.com/elyukai/optolith-client/issues/636)
|
||||
|
||||
### API
|
||||
|
||||
There are important and breaking changes coming with the next release, so if you use the Optolith source or save data, I'd recommend to come over to [Discord](https://discord.gg/wfdgB9g) so that I can tell you what is going to be changed in detail. Since more and more people depend on Optolith's data I will now follow semantic versioning strictly so that you can rely on the compatibility of source files just by checking out the version number. There will be a new format for heroes as well as for the source data. You can also give feedback on the changes, since they are not *completely* done yet – but I still think it would make sense to check out the changes as early as possible. The next major version is still a bit away, so you'll have plenty of time to adjust your software.
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.3.2
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- La description de la sélection des techniques de combat pour les variantes de professions n'est pas rendue correctement. #539
|
||||
- Les professions ne tenaient pas compte des conditions prérequises. #507
|
||||
- Le champ de texte "Titre" des données personnelles était mal renseigné. #492
|
||||
- *Religion* avait une VC 6 pour la plupart des professions cléricales. #475
|
||||
- Les États débordaient de la feuille de personnage en PDF mais pas dans Optolith. #473
|
||||
- Le `config.json` a encore provoqué des erreurs pour de nombreux utilisateurs. Le problème devrait être réglé, finalement. #476
|
||||
- Dans de très rares cas, des parties d'un fichier de héros ou la totalité d'un fichier de héros ont été effacées. C'est pour cette raison que la sauvegarde des héros a été complètement réécrite et condensée pour éviter d'autres problèmes. #477
|
||||
- Rajout du dialecte Garethi manquant *Nostrien*. #480
|
||||
- Rectification des licences tierces. #471
|
||||
- Rectification des désavantages avec des options à choisir qui ne pouvaient pas être achetés. #470
|
||||
|
||||
### Known Issues
|
||||
|
||||
- Some of the entries with more complex prerequisites might *display* them wrong but *handle* them correct.
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- Dans la version française, vous ne pouviez pas créer de personnages, car vous ne pouviez choisir aucune publication, aucune race, ... #449
|
||||
- Il y a eu une erreur due à des "clés inconnues" dans la configuration. #450
|
||||
- Sur la feuille de personnage, les états n'étaient affichés dans une seule colonne que si le nombre d'états était pair. #448
|
||||
|
||||
## 1.3.0
|
||||
|
||||
Version initiale.
|
||||
@@ -1,22 +0,0 @@
|
||||
## 1.4.0
|
||||
|
||||
Primo rilascio della versione italiana.
|
||||
|
||||
### API
|
||||
|
||||
Ci sono importanti modifiche in questa nuova versione, che cambiano anche il modo in cui sono memorizzati i dati. Se fai uso dei file memorizzati da Optolith, è possibile fare un salto su [Discord](https://discord.gg/wfdgB9g) in modo da avere più informazioni sulle modifiche nel dettaglio.
|
||||
Siccome sempre più persone fanno uso dei dati di Optolith, da questa versione in poi seguiremo le regole del *versionamento semantico*, in modo da permettere agli utenti di fare affidamento alla compatibilità dei file sorgente e dei file dei dati, verificandone soltanto il numero di versione.
|
||||
Ci sarà un nuovo formato di dati sia per gli eroi memorizzati che per i dati sorgente.
|
||||
Le modifiche non sono ancora finalizzate e la nuova versione maggiore è ancora lontana, ma è consigliato aggiornarsi al nuovo formato di dati il prima possibile.
|
||||
|
||||
### Thank you
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
@@ -1,102 +0,0 @@
|
||||
## 1.4.0
|
||||
|
||||
### Nieuwe features
|
||||
|
||||
- Italian translation [#667](https://github.com/elyukai/optolith-client/issues/667) [#610](https://github.com/elyukai/optolith-client/issues/610)
|
||||
- Optional paper-style background for character sheet. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Character Sheet view zoom option. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Add/Subtract money from purse. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
- Weight is now shown in an own column in equipment tab. [#305](https://github.com/elyukai/optolith-client/issues/305)
|
||||
- Unnecessary combat sheets are not used anymore, it now depends on which armor the character has. It defaults to the normal armor if no armor is present. [#407](https://github.com/elyukai/optolith-client/issues/407)
|
||||
- You can now click on entry names as well to show it's rules text. [#556](https://github.com/elyukai/optolith-client/issues/556)
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Variants of profession *Blessed One of Boron* had wrong skill modifications. [#548](https://github.com/elyukai/optolith-client/issues/548)
|
||||
- Profession *White Mage (Sword & Staff)* had a wrong AP value and an additional skill modification. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Fixed common professions of a culture will now be shown even if they are not from the **Core Rules**. [#563](https://github.com/elyukai/optolith-client/issues/563)
|
||||
- The interface could be scaled down. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Weapons of the combat technique *Lances* were not shown on character sheet. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Attributes ignored the minimum caused by high skill ratings. The maximum of skill rating is the highest linked attribute + 2, so the highest attribute is required to have a minimum value of SR − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
|
||||
### API
|
||||
|
||||
There are important and breaking changes coming with the next release, so if you use the Optolith source or save data, I'd recommend to come over to [Discord](https://discord.gg/wfdgB9g) so that I can tell you what is going to be changed in detail. Since more and more people depend on Optolith's data I will now follow semantic versioning strictly so that you can rely on the compatibility of source files just by checking out the version number. There will be a new format for heroes as well as for the source data. You can also give feedback on the changes, since they are not *completely* done yet – but I still think it would make sense to check out the changes as early as possible. The next major version is still a bit away, so you'll have plenty of time to adjust your software.
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.3.2
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Beschrijving van gevechtstechnieken selectie voor beroepsvarianten wordt niet correct getoond. #539
|
||||
- Beroepen reageerden niet correct op vereisten. #507
|
||||
- Het tekstveld "Titel" in de persoonlijk data had een verkeerd label. #492
|
||||
- *Goden & Kulten* was 6 voor de meeste Gewijde beroepen. #475
|
||||
- Statussen waren te lang op je helden dokument PDF maar niet in Optolith. #473
|
||||
- De `config.json` had nog altijd fouten voor veel gebruikers. Het probleem zou eindelijk opgelost moeten zijn. #476
|
||||
- In hele zeldzame situaties werd de helden file geheel of gedeeltelijk verwijderd. Het saven van helden is geheel herschreven en compacter gemaakt om verdere problemen te voorkomen. #477
|
||||
- Voeg het ontbrekende Garethi dialect *Nostrian* toe. #480
|
||||
- Vereisten van de Competentie Energie Controle zijn aangepast. #471
|
||||
- Derde parij licenties zijn aangepast. #471
|
||||
|
||||
### Bekende problemen
|
||||
|
||||
- Sommige elementen met complexe vereisten worden verkeerd getoont maar wel correct behandeld.
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- In de franse versie kon je geen helden maken, want je kon geen publicaties, ras, ... kiezen #449
|
||||
- Er werd een error gemeld voor "unknown keys" in de config. #450
|
||||
- Op je helden document werden staten enkel getoond in een enkele kolom als het aantal staten even was. #448
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Nieuwe features
|
||||
|
||||
- Franse versie (incl. Regelwerk) #34
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
- Het zoekveld in de Wiki wordt niet langer gewist als men van categorie wisselt. #286
|
||||
- Als het importeren van een held mislukt wordt nu gespecifieerd wat het probleem was (incompatibele versie, onbekend bestand, enz.) #343 #394
|
||||
- Volledig nieuwe databankstructuur. Daardoor 50% snellere laadtijd.
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- De taaltabel vervormde de lay-out als Hjaldings/Saga-Thorwals actief was. #419
|
||||
- De teksten bij de persoonlijke data op het heldendocument werden gedeeltelijk beknot. #413
|
||||
- Het totale gewicht is nu gealigneerd op de het uitrustingsdocument. #291
|
||||
- Een voorwerp kon niet worden opgenomen als geïmproviseerd wapen. #290
|
||||
- De infotabel voor routineproeven was niet correct gealigneerd. #214
|
||||
- Angst voor: keuze/eigen tekst werd niet getoond op het heldendocument. #213
|
||||
- Men kon geen prenten importeren van een server als karaktertekening. #229
|
||||
- Het toevoegen van AP werkte niet als men het invoerveld niet verliet. #341
|
||||
- Wapens die geen schade veroorzaken, tonen niet langer enkel D als schade. #353
|
||||
- Geen talentvermindering op *Spoorzoeken* meer mogelijk bij jager (Premiejager). #357
|
||||
- Com *Uithaal* toonde GtW Zwaarden dubbel. #355
|
||||
- Een geëxporteerde held met een BOM kon niet worden gelezen. #37
|
||||
- Veranderingen aan voorwerpen konden onder bepaalde omstandigheden niet worden bewaard. #389
|
||||
- Fout opgelost waarbij het veld "waar gedragen" niet bewaard werd bij voorwerpen. #369
|
||||
- De maximale waarde voor gevechtstechnieken door de hoofdeigenschap werkt weer. #379
|
||||
- Typo in ATTRIBUTO (Lichaamskracht), tweede uitbreiding verbeterd. #366
|
||||
- De toepassingsconfiguratie veroorzaakt geen fouten meer. #358
|
||||
|
||||
## 1.2.0
|
||||
|
||||
Eerste Nederlandstalige versie.
|
||||
@@ -1,5 +0,0 @@
|
||||
const _require = require("esm")(module)
|
||||
|
||||
process.once('loaded', () => {
|
||||
global.require = _require
|
||||
})
|
||||
@@ -1,2 +0,0 @@
|
||||
require = require('esm')(module)
|
||||
require('./main.js')
|
||||
File diff suppressed because one or more lines are too long
+25
-32
@@ -2,25 +2,25 @@
|
||||
"name": "optolith-client",
|
||||
"version": "0.1.0",
|
||||
"sources": [
|
||||
{ "dir": "deploy" },
|
||||
{ "dir": "src" },
|
||||
{ "dir": "src/App/Actions" },
|
||||
{ "dir": "src/App/Constants" },
|
||||
{ "dir": "src/App/Containers" },
|
||||
{ "dir": "src/App/Models" },
|
||||
{ "dir": "src/App/Reducers" },
|
||||
{ "dir": "src/App/Selectors" },
|
||||
{ "dir": "src/App/Utilities" },
|
||||
{ "dir": "src/App/Utilities/__tests__", "type": "dev" },
|
||||
{ "dir": "src/App/Utilities/YAML" },
|
||||
{ "dir": "src/App/Views" },
|
||||
{ "dir": "src/App/Views/Universal" },
|
||||
{ "dir": "src/Control" },
|
||||
{ "dir": "src/Data" },
|
||||
{ "dir": "src/Data/__tests__", "type": "dev" },
|
||||
{ "dir": "src/Debug" },
|
||||
{ "dir": "src/System" },
|
||||
{ "dir": "src/shims" }
|
||||
{ "dir": "src", "subdirs": [
|
||||
{ "dir": "Common" },
|
||||
{ "dir": "Dependencies" },
|
||||
{ "dir": "External" },
|
||||
{ "dir": "IO" },
|
||||
{ "dir": "Localization" },
|
||||
{ "dir": "Rated", "subdirs": [
|
||||
{ "dir": "__tests__", "type": "dev" },
|
||||
{ "dir": "ArcaneSpellworks" },
|
||||
{ "dir": "Liturgies" },
|
||||
{ "dir": "MagicalActions" }
|
||||
] },
|
||||
{ "dir": "Rules" },
|
||||
{ "dir": "Scenes" },
|
||||
{ "dir": "Sources" },
|
||||
{ "dir": "Utilities", "subdirs": [
|
||||
{ "dir": "Abstractions" }
|
||||
] }
|
||||
] }
|
||||
],
|
||||
"package-specs": {
|
||||
"module": "commonjs",
|
||||
@@ -28,24 +28,17 @@
|
||||
},
|
||||
"suffix": ".bs.js",
|
||||
"bs-dependencies": [
|
||||
"@glennsl/bs-json",
|
||||
"@glennsl/bs-jest",
|
||||
"reason-react"
|
||||
"@rescript/react",
|
||||
"bs-decoders",
|
||||
"reductive"
|
||||
],
|
||||
"reason": {
|
||||
"react-jsx": 3
|
||||
},
|
||||
"warnings": {
|
||||
"error" : "+101"
|
||||
"number": "+A-42-48",
|
||||
"error": "+A-3-4-27-32-41-44-102"
|
||||
},
|
||||
"namespace": true,
|
||||
"refmt": 3,
|
||||
"gentypeconfig": {
|
||||
"module": "commonjs",
|
||||
"language": "typescript",
|
||||
"shims": {
|
||||
"Js": "Js",
|
||||
"ReasonPervasives": "ReasonPervasives"
|
||||
}
|
||||
}
|
||||
"namespace": true
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Generated by BUCKLESCRIPT, PLEASE EDIT WITH CARE
|
||||
// Generated by ReScript, PLEASE EDIT WITH CARE
|
||||
'use strict';
|
||||
|
||||
|
||||
var WINDOWS = { };
|
||||
var WINDOWS = {};
|
||||
|
||||
var LINUX = { };
|
||||
var LINUX = {};
|
||||
|
||||
var MAC = { };
|
||||
var MAC = {};
|
||||
|
||||
var Platform = {
|
||||
WINDOWS: WINDOWS,
|
||||
@@ -14,7 +14,7 @@ var Platform = {
|
||||
MAC: MAC
|
||||
};
|
||||
|
||||
var Config = { };
|
||||
var Config = {};
|
||||
|
||||
var ElectronBuilder = {
|
||||
Platform: Platform,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { writeFile } from 'fs';
|
||||
import sass from 'sass';
|
||||
|
||||
sass .render (
|
||||
{ file: 'src/Main.scss', outputStyle: 'compressed' },
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
writeFile ('app/main.css', result.css, err => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.log('SCSS successfully bundled!');
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
# Decoding
|
||||
|
||||
Here you'll find some useful information about the design of the YAML decoders.
|
||||
|
||||
## Overview
|
||||
|
||||
Decoding the database is splitted into parsing the YAML files and decoding the result. Parsing is done by the `Yaml_Parse` module and the `Yaml_Decode` module controls the decoding of the various entry types. The `Yaml` module then combines all functions into a single exported function.
|
||||
|
||||
## Decode Entry Types
|
||||
|
||||
### Decoder structure
|
||||
|
||||
All decodable main types share a common decoder layout. All values only relevant while decoding are inside a `Decode` module at the same level as the type it is the decoder for.
|
||||
|
||||
```ml
|
||||
(** Since this is the actual type, it's not really part of the decoding pipeline
|
||||
but I'll keep it here to make the definitions complete. *)
|
||||
type t = {
|
||||
(* ... *)
|
||||
}
|
||||
|
||||
module Decode = struct
|
||||
open Json.Decode
|
||||
open JsonStrict
|
||||
|
||||
type translation = { (* ... language-specific properties *) }
|
||||
|
||||
let translation json =
|
||||
{
|
||||
(* ... decode language-specific properties *)
|
||||
}
|
||||
|
||||
type multilingual = {
|
||||
(* ... language-independent properties *)
|
||||
translations : translation TranslationMap.t;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
{
|
||||
(* ... decode language-independent properties *)
|
||||
translations =
|
||||
json |> field "translations" (TranslationMap.Decode.t translation);
|
||||
}
|
||||
|
||||
let make_assoc locale_order json =
|
||||
let open Option.Infix in
|
||||
json |> multilingual |> fun multilingual ->
|
||||
multilingual.translations |> TranslationMap.preferred locale_order
|
||||
<&> fun (translation : translation) ->
|
||||
( multilingual.id,
|
||||
{
|
||||
(* ... merge language-independent and language-specific properties *)
|
||||
} )
|
||||
end
|
||||
```
|
||||
|
||||
#### Decoding types
|
||||
|
||||
Since language-specific values in the YAML files are always defined in dictionaries where the keys map to the locale identifiers, we always split the language-specific and the language-independent parts.
|
||||
|
||||
The language-specific part of a type is always defined as type `translation`. The corresponding `translation` decoder function is later used to decode the translation dictionary, also called *translation map*.
|
||||
|
||||
The language-independent part of a type is defined as type `multilingual` and usually has the property `translations`, which is the dictionary of all translations. The corresponding `multilingual` decoder function uses the `translation` decoder together with the translation map decoder from the `TranslationMap` module to fully decode the dictionary of translations.
|
||||
|
||||
Using a list of preferred locales, sorted by preference, (`Locale.Order.t`) we can retrieve the most preferred translation using `TranslationMap.preferred` and then combine the translation values and the multilingual values to form the final record type. Following OCaml conventions, this function is called `make`.
|
||||
|
||||
Often, in order to be used more easily, the `make` function returns a pair of the respective entry's identifier and the entry itself to create a `Map` more easily. To hint at this specific use, the function is then called `make_assoc`. If a nested type is only decoded in a context of collection type (like a list), this decoder can be provided using a `make` variant as well (in this case, `make_list`).
|
||||
|
||||
If other decodable types are nested withing a type, the `multilingual` function usually takes the list of preferred locales as well to be able to pass it down to the nested types' decoders.
|
||||
|
||||
### `Decode` module visibility conventions
|
||||
|
||||
Only the `make` function or its variant should be exposed.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- The language-specific tyüe is always named `translation`.
|
||||
- The record containing all language-independent values as well as the translation map is called `multilingual` and the translation map is at the property `translations` (just like in the source).
|
||||
- A decoder for a specific type has the same name as the type.
|
||||
- The function resolving the translation map and returning the actual main type is called `make`.
|
||||
- A potential helper function that decodes the type as part of a collection is called `make_c`, where `c` is the name of the collection, e.g. `make_list`.
|
||||
@@ -0,0 +1,8 @@
|
||||
# *Dynamic* and *Static*
|
||||
|
||||
In entry type definition files, you'll often see the content is wrapped in a `Dynamic` and a `Static` module. What does that mean?
|
||||
|
||||
- **Static** values come from the YAML files, so they have been defined there and they will not change while the program runs.
|
||||
- **Dynamic** values are kind of like *instances* of the *static* values. They are part of the heroes and define how an entry is configured for that hero.
|
||||
|
||||
For example, a `Spell.Static.t` type is the static representation of a spell: It contains it's name, IC, duration, target category, and so on. The `Spell.Dynamic.t` type is the dynamic representation of the spell for a specific hero: Is the spell active? If yes, which SR does it have?
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generating module code
|
||||
|
||||
## Convert syntax
|
||||
|
||||
Using `bsrefmt`, you can convert between syntaxes:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsrefmt path/to/file --print=syntax
|
||||
```
|
||||
|
||||
`syntax` can be `res`, `re` and `ml`. The converted code from the referenced file will be output to command line.
|
||||
|
||||
## Interface files
|
||||
|
||||
Using `bsc`, you can generate an interface file in OCaml syntax if only the implementation file is present:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
To generate in Reason syntax, provide the `-bs-re-out` flag.
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc -bs-re-out ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
The generated code will be output to command line.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Labelled arguments syntax in OCaml
|
||||
|
||||
The syntax for labelled arguments might not always be obvious. Below you'll find a cheatsheet for the syntax in both implementations and signatures.
|
||||
|
||||
## Basic
|
||||
|
||||
### Definition
|
||||
|
||||
```ocaml
|
||||
let f ~param:param = param
|
||||
```
|
||||
|
||||
You can omit the variable name after the label itself if its the same as the label.
|
||||
|
||||
```ocaml
|
||||
let f ~param = param
|
||||
```
|
||||
|
||||
### Call
|
||||
|
||||
```ocaml
|
||||
let x = f ~param:value
|
||||
```
|
||||
|
||||
### Signature
|
||||
|
||||
```ocaml
|
||||
val f : ~param:'a -> 'a
|
||||
```
|
||||
|
||||
## Optional
|
||||
|
||||
```ocaml
|
||||
let f ?param:param () = param
|
||||
let f ?param () = param
|
||||
|
||||
val f : ?param:'a -> unit -> 'a
|
||||
```
|
||||
|
||||
You can also provide a default value.
|
||||
|
||||
```ocaml
|
||||
let f ?(param=0) () = param
|
||||
let f ?param:(param=0) () = param
|
||||
```
|
||||
|
||||
A function defining optional arguments must also define at least one non-labelled argument. A typical parameter for this use case is unit.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ~param:0 () (* x = Some 0 *)
|
||||
```
|
||||
|
||||
You may also forward an optional type to an optional argument.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ?param:None () (* x = None *)
|
||||
let x = f ?param:(Some 0) () (* x = Some 0 *)
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
# Structure
|
||||
|
||||
- `src` – The source code main folder, containing program entry points.
|
||||
- `Common` – Types and functions associated with multiple ‘namespaces’.
|
||||
- `Database` – The flat-file database containing all static data from the source books.
|
||||
- `Dependencies` – Types and functions for working with dependencies between entries and the prerequisites they are based on.
|
||||
- `External` – Bindings to external libraries via FFI.
|
||||
- `Localization` – Types and functions related to different languages in the app and the source books.
|
||||
- `Rated` – All data types for entries with *ratings* (attributes, skills or combat techniques) and related types and logic.
|
||||
- `ArcaneSpellworks` – All data types for arcane spellworks (spells and rituals) and related types and logic.
|
||||
- `Liturgies` – All data types for liturgies (liturgical chants and ceremonies) and related types and logic.
|
||||
- `MagicalActions` – All data types for magical actions (curses, Elven magical songs, …) and related types and logic.
|
||||
- `Rules` – All data types for rule entries.
|
||||
- `Sources` – Types and functions dealing with data related to source books: Source books themselves, references, availability by book and errata.
|
||||
- `Utilities` – Utility functions and types for working with standard types or I/O.
|
||||
- `Abstractions` – Utility functions and types for working with type abstractions such as monads that can be used to create similar functions for different container types.
|
||||
@@ -1,3 +1,48 @@
|
||||
## 1.4.2
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Der Start der Anwendung war viel langsamer als in Version 1.3.2. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Fehlerbehebungen
|
||||
|
||||
- Das Maximum von Kampftechniken konnte höher sein als es die Regeln eigentlich erlauben. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- Der Abstand von Overlays – also z. B. wenn Vorteile hinzugefügt werden – nach oben war zu klein. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- Wenn eine Profession es erlaubt hat, aus einer Menge an Kampftechniken eine zweite für einen niedrigeren Bonus als den der ersten auszuwählen, wurde dieser niedrigere Bonus nicht auf den Charakter angewandt. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- Die *Fackel* hatte als L+S KK 15 statt KK 14. [#752](https://github.com/elyukai/optolith-client/issues/752) [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- Der Schadensbonus durch L+S wurde bei Waffen, die keinen festen Schaden hatten, nicht berücksichtigt. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- Es konnte nur ein Talentstil zur gleichen Zeit aktiv sein, obwohl es keine Einschränkung diesbezüglich gibt. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Die Profession *Weißmagier (Akademie Schwert und Stab zu Gareth)* aus dem **Regelwerk** hatte falsche AP-Kosten und *Sternkunde 4* statt *Alchemie 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Das Talentefeld für Tierbegleiter auf dem Charakterbogen was zu klein, um alle relevanten Talente aufnehmen zu können. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- Im Regeltext für das *Großzelt, 12 Personen* fehlte ein Wort. [#739](https://github.com/elyukai/optolith-client/issues/739)
|
||||
- SF *Anhänger des Güldenen* erlaubte Liturgien bis Steigerungsfaktor C, obwohl nur welche bis B erlaubt sind. [#754](https://github.com/elyukai/optolith-client/issues/754)
|
||||
- SF *Scholar des Magierkollegs zu Honingen* fehlte. [#726](https://github.com/elyukai/optolith-client/issues/726)
|
||||
- SF *Havena-Stil* fehlte. [#740](https://github.com/elyukai/optolith-client/issues/740)
|
||||
- Der Name des Feenpaktgeschenks *Ruf des Waldes* hatte einen Schreibfehler. [#755](https://github.com/elyukai/optolith-client/issues/755)
|
||||
- Profession *Gildenloser Magier nach Vadif sal Karim* hatte eine Bonus auf den falschen *Hagelschlag*-Zauber. [#772](https://github.com/elyukai/optolith-client/issues/772)
|
||||
- Der Text der dritten Erweiterung des Zaubers *Klarheit des Eises* hatte eine falsche Zeiteinheit in der Zeitangabe. [#766](https://github.com/elyukai/optolith-client/issues/766)
|
||||
- Der SF *Spiritualistinnen* hatte falsche erweiterte Sonderfertigkeiten. [#763](https://github.com/elyukai/optolith-client/issues/763)
|
||||
- SF *Klingensturm* hatte falsche Kampftechniken angegeben. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Bilder mit großgeschriebenen Dateiendungen wurden als Portraits nicht akzeptiert. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- Wenn ein Portrait fehlte, wurde auf dem Charakterbogen das Upload-Icon angezeigt. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Auf dem Mac wurden, wenn eine Papiertextur für den Charakterbogen ausgewählt wurde, zusätzliche leere Seiten generiert. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
- Der Effekttext der SF *Bindung des Dolches* hatte einen Schreibfehler. [#773](https://github.com/elyukai/optolith-client/issues/773)
|
||||
|
||||
### Danke
|
||||
|
||||
Zu guter Letzt ein großes **Danke** an alle, die über verschiedeste Kanäle Feedback geben und helfen, sowie an die folgenden Menschen auf GitHub, die an dieser Version mitgewirkt haben!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Fehlerbehebungen
|
||||
|
||||
- Italienisch war nicht verfügbar.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Features
|
||||
@@ -9,6 +54,7 @@
|
||||
- Optionaler Charakterbogenhintergrund in Papieroptik. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Zoom-Option für Charakterbogen-Ansicht. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Geld zum Geldbeutel hinzufügen oder davon abziehen. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
- Eigene Vorteile, Nachteile und Sonderfertigkeiten mit eigenem Namen und AP-Wert erstellen (temporäre Lösung). [#632](https://github.com/elyukai/optolith-client/issues/632)
|
||||
|
||||
### Verbesserungen
|
||||
|
||||
@@ -40,6 +86,27 @@
|
||||
- Schelmenstreiche zeigten keine Wirkungsdauer an, obwohl sie eine besitzen. [#663](https://github.com/elyukai/optolith-client/issues/663)
|
||||
- Waffen der Kampftechnik *Lanzen* wurden nicht auf dem Heldenbogen abgezeigt. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Eigenschaften ignorierten das Minimum, welches durch höhere Fertigkeitswerte entsteht. Das Maximum für Fertigkeitswerte ist die höchste an der Probe beteiligte Eigenschaft + 2, wodurch die höchste Eigenschaft mindestens FW − 2 sein muss. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- Das neue Anwendungsgebiet *Fachpublikationen* für das Talent *Rechtskunde* hatte eine falsche Sonderfertigkeitsvoraussetzung. [#680](https://github.com/elyukai/optolith-client/issues/680)
|
||||
- Geweihte des Namenlosen konnten keine Segnungen kaufen. [#657](https://github.com/elyukai/optolith-client/issues/657)
|
||||
- Einige Listeneinträge waren breiter als die eigentliche Liste. [#683](https://github.com/elyukai/optolith-client/pull/683)
|
||||
- SF *Kind der Natur* und ähnliche SFs erlaubten nur ein Talent. [#649](https://github.com/elyukai/optolith-client/issues/649)
|
||||
- Listenüberschriften waren standardmäßig nicht zentriert. [#637](https://github.com/elyukai/optolith-client/issues/637)
|
||||
- SF *Fachwissen* hatte unerwartete Zeichen im generierten Namen. [#611](https://github.com/elyukai/optolith-client/issues/611)
|
||||
- Permanent ausgegebene LE verbot den Kauf weiterer LeP. [#606](https://github.com/elyukai/optolith-client/issues/606)
|
||||
- Wenn KO auf 8 war und keine Einträge von ihr abhingen, konnten keine weiteren LeP gekauft werden. [#694](https://github.com/elyukai/optolith-client/issues/694)
|
||||
- *Aspektkenntnis*-SFs konnten trotz des Minimums von mindestens drei Liturgien des Aspekts auf FW 10 oder höher nicht gekauft werden. [#591](https://github.com/elyukai/optolith-client/issues/591)
|
||||
- Der Zaubertrick *Hexenblick* konnte von allen Zauberern gekauft werden. [#377](https://github.com/elyukai/optolith-client/issues/377)
|
||||
- Zaubertänzer konnten keine Zaubertänze kaufen. [#594](https://github.com/elyukai/optolith-client/issues/594)
|
||||
- Die Varianten der Zaubertänzer und -barden berücksichtigten ihre jeweiligen Kultur- und ggf. Geschlechtsvoraussetzungen nicht. [#565](https://github.com/elyukai/optolith-client/issues/565)
|
||||
- Wenn bei einer Profession neben der Auswahl einer oder mehrerer Kampftechniken für einen bestimmten KtW eine oder mehrere weitere Kampftechniken für einen niedrigeren KtW ausgewählt werden sollen (z. B. bei der Profession *Soldat*), war dies nicht möglich. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- Die SFs *Gift melken (...)* beschrieben ein neues Einsatzgebiet, gemeint ist eine *Einsatzmöglichkeit*. [#651](https://github.com/elyukai/optolith-client/issues/651)
|
||||
- Die Beschreibung des Zaubers *Gletscherform* entsprach nicht dem Text aus **Aventurische Magie II**. [#654](https://github.com/elyukai/optolith-client/issues/654)
|
||||
- Meldungen des Updaters sowie der Suche-nach-Updates-Button sind nun auf macOS deaktiviert, da dort aktuell keine automatischen Updates möglich sind. [#589](https://github.com/elyukai/optolith-client/issues/589)
|
||||
- Die SF *Feindgespür* nannte falsche Kampftechniken, für die die SF gilt. [#658](https://github.com/elyukai/optolith-client/issues/658)
|
||||
- Auto-Update-Warnungen wurden auf Linux gezeigt selbst wenn keine direkten Auto-Updates möglich waren. [#573](https://github.com/elyukai/optolith-client/issues/573)
|
||||
- Bei der SF *Zugvögel* und SFs mit vergleichbaren Effekten wurden die zusätzlich ermöglichten Liturgieplätze durch passende aktive Liturgien, die allerdings auch für die eigentliche Tradition der Geweihten verfügbar waren, blockiert. [#677](https://github.com/elyukai/optolith-client/issues/677)
|
||||
- Die SF *Giftverstärkung* hatte einen falschen Regeltext. [#713](https://github.com/elyukai/optolith-client/issues/713)
|
||||
- Beim Exportieren eines Charakters als JSON wurde im Speichern-Dialog die Dateiendung nicht an den vorgeschlagenen Dateinamen angefügt. [#718](https://github.com/elyukai/optolith-client/issues/718)
|
||||
|
||||
### API
|
||||
|
||||
@@ -1,3 +1,45 @@
|
||||
## 1.4.2
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Start-up was way slower than in 1.3.2. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Added some missing UI translations. [#723](https://github.com/elyukai/optolith-client/issues/723)
|
||||
- Added missing aspects for **Gods of Aventuria I**. [#724](https://github.com/elyukai/optolith-client/issues/724)
|
||||
- Added missing special ability groups for **Gods of Aventuria I**. [#725](https://github.com/elyukai/optolith-client/issues/725)
|
||||
- SA *Two Blessings at Once* had the wrong rules text. [#750](https://github.com/elyukai/optolith-client/issues/750)
|
||||
- Combat Techniques maximum CtR could be higher that allowed by rules. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- The top margin of overlay windows – like when adding advantages – was too small. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- If a profession allowed to select a second combat technique from a set of combat techniques where the bonus was lower than the bonus for the first combat technique, the selected lower bonus was not applied to the character. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- The *Torch* had P+T STR 15 instead of STR 14. [#752](https://github.com/elyukai/optolith-client/issues/752) [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- The P+T damage bonus was not added to weapons without flat damage. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- Profession *White Mage (Gareth Academy of Sword and Staff)* from the **Core Rules** had a wrong AP value and *Astronomy 4* instead of *Alchemy 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Creature skills text field on PDF export was too short to fit all relevant skills of a creature. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- SA *Followers of the Golden One* allowed liturgical chants up to improvement cost C instead of up to B. [#754](https://github.com/elyukai/optolith-client/issues/754)
|
||||
- Profession *Guildless Mage (Vadif sal Karim’s Student)* had a bonus on the wrong *Hailstorm and Roar of Thunder* spellwork. [#772](https://github.com/elyukai/optolith-client/issues/772)
|
||||
- SA *Bladestorm* had wrong applicable combat techniques. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Some books were missing the weight. [#760](https://github.com/elyukai/optolith-client/issues/760)
|
||||
- Images with uppercase file endings have not been accepted as portrait images. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- The upload icon was shown in place of the portrait image on the character sheet if no portrait was defined. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- On the Mac, if paper background was activated, additional blank pages were generated for the PDF output. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to all of you who give feedback and help out on various channels as well as to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Italian was not available.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Features
|
||||
@@ -7,6 +49,7 @@
|
||||
- Optional paper-style background for character sheet. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Character Sheet view zoom option. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Add/Subtract money from purse. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
- Create custom advantages, disadvantages and special abilities with custom name and AP value (temporary solution). [#632](https://github.com/elyukai/optolith-client/issues/632)
|
||||
|
||||
### Enhancements
|
||||
|
||||
@@ -22,6 +65,23 @@
|
||||
- The interface could be scaled down. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Weapons of the combat technique *Lances* were not shown on character sheet. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Attributes ignored the minimum caused by high skill ratings. The maximum of skill rating is the highest linked attribute + 2, so the highest attribute is required to have a minimum value of SR − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- The new application *Professional Publications* for skill *Law* had a wrong special ability prerequisite. [#680](https://github.com/elyukai/optolith-client/issues/680)
|
||||
- Some list items were wider than the actual list. [#683](https://github.com/elyukai/optolith-client/pull/683)
|
||||
- List headers were not centered by default. [#637](https://github.com/elyukai/optolith-client/issues/637)
|
||||
- Permanently spent LP did not allow additional LP to be bought. [#606](https://github.com/elyukai/optolith-client/issues/606)
|
||||
- Additional LP could not be added if CON is 8 and nothing depends on it. [#694](https://github.com/elyukai/optolith-client/issues/694)
|
||||
- SA *Aspect Knowledge* did not show up even with three chants on SR 10 or higher. [#591](https://github.com/elyukai/optolith-client/issues/591)
|
||||
- Cantrip Witch's Gaze was selectable by all spell casters. [#377](https://github.com/elyukai/optolith-client/issues/377)
|
||||
- Arcane Dancers could not add magical dances. [#594](https://github.com/elyukai/optolith-client/issues/594)
|
||||
- Select options of traditions Arcane Bard and Arcane Dancer did not require their respective culture and possible sex. [#565](https://github.com/elyukai/optolith-client/issues/565)
|
||||
- If, in addition to the selection of one or more fighting techniques for a certain CtR, one or more other fighting techniques should be selected for a lower CtR (e.g. in the profession *Soldier*), it was not possible. [#647](https://github.com/elyukai/optolith-client/issues/647)
|
||||
- Certain values on character sheet were not floored. [#655](https://github.com/elyukai/optolith-client/issues/655)
|
||||
- Chain Mail was available twice. [#570](https://github.com/elyukai/optolith-client/issues/570)
|
||||
- Scale Mail was available twice and had a typo. [#569](https://github.com/elyukai/optolith-client/issues/569)
|
||||
- Updater checks and the check for updates button are now disabled on macOS since updates are not possible on macOS currently. [#589](https://github.com/elyukai/optolith-client/issues/589)
|
||||
- Wrong applicable combat techniques for SA *Enemy Sense*. [#658](https://github.com/elyukai/optolith-client/issues/658)
|
||||
- Auto-update alerts were enabled on Linux even when there were no direct auto-updates available. [#573](https://github.com/elyukai/optolith-client/issues/573)
|
||||
- The save dialog for exporting a character as JSON did not add the file extension to the suggested file name. [#718](https://github.com/elyukai/optolith-client/issues/718)
|
||||
|
||||
### API
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
## 1.4.2
|
||||
|
||||
### Améliorations
|
||||
|
||||
- Suppression des *N/D* dans les tableaux des sorts et des liturgies sur la feuille de personnage afin que les valeurs puissent être ajoutées à la main. [#747](https://github.com/elyukai/optolith-client/issues/747)
|
||||
- Le démarrage était beaucoup plus lent qu'en 1.3.2. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- La VTC maximale des techniques de combat pourrait être supérieure à celle autorisée par les règles. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- La marge supérieure des fenêtres superposées – comme lors de l'ajout d'avantages – était trop petite. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- Le bonus de dommages Q+SD n'était pas ajouté aux armes sans dommages fixes. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- La profession *Magicien blanc (Académie de Gareth de l'épée et du bâton)* des **règles de base** avait une mauvaise valeur de PAV et *Astronomie 4* au lieu de *l'Alchimie 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- La zone de texte "compétences de la créature" sur l'export PDF était trop courte pour correspondre à toutes les compétences pertinentes d'une créature. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- Les images dont la fin de fichier était en majuscules n'étaient pas acceptées comme images de portrait. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- L'icône de téléchargement était affichée à la place de l'image du portrait sur la fiche de personnage si aucun portrait n'était défini. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Sur Mac, si le fond papier était activé, des pages vierges supplémentaires étaient générées pour la sortie PDF. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Remerciements
|
||||
|
||||
Enfin et surtout, un grand **merci** à tous ceux qui font des retours et donnent leur aide sur les différents canaux ainsi qu'aux personnes ci-dessous sur GitHub qui ont contribué à cette version !
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- L'italien n'était pas disponible.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
Veuillez vérifier les personnages pour lesquels vous avez ajouté la CS *intuition tactique* ou la CS *détection des embuscades*. Vous remarquerez qu'elle n'a pas son ancien nom. Ceci est dû à une erreur (mentionnée ci-dessous) où les noms de ces deux capacités spéciales ont été échangés. La représentation interne ne peut pas être modifiée et les entrées doivent donc être "renommées". Cela devrait également corriger les prérequis "incorrects" des deux entrées.
|
||||
|
||||
### Fonctionnalités
|
||||
|
||||
- Traduction italienne [#667](https://github.com/elyukai/optolith-client/issues/667) [#610](https://github.com/elyukai/optolith-client/issues/610)
|
||||
- Fond de papier facultatif pour la feuille de personnages. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- Option de zoom de la vue sur la feuille de personnages. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Ajouter/Soustraire de l'argent du porte-monnaie. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
- Créez des avantages, des inconvénients et des capacités spéciales sur mesure avec un nom et une valeur en PAV (solution temporaire). [#632](https://github.com/elyukai/optolith-client/issues/632)
|
||||
|
||||
### Améliorations
|
||||
|
||||
- Le poids est maintenant indiqué dans une colonne spécifique dans l'onglet équipement. [#305](https://github.com/elyukai/optolith-client/issues/305)
|
||||
- Les feuilles de combat inutiles ne sont plus utilisées, cela dépend maintenant de l'armure dont dispose le personnage. Par défaut, l'armure normale est utilisée si aucune armure n'est présente. [#407](https://github.com/elyukai/optolith-client/issues/407)
|
||||
- Vous pouvez maintenant cliquer sur les noms des entrées pour afficher le texte de leurs règles. [#556](https://github.com/elyukai/optolith-client/issues/556)
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- Les variantes de la profession de *Prêtre de Boron* ont eu de mauvaises modifications de compétences. [#548](https://github.com/elyukai/optolith-client/issues/548)
|
||||
- La profession *Magicien blanc (Epée & Bâton)* avait une valeur en PAV erronée et une modification de compétence supplémentaire. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Les professions communes fixes d'une culture seront désormais affichées même si elles ne sont pas issues des **règles de base**. [#563](https://github.com/elyukai/optolith-client/issues/563)
|
||||
- L'interface peut être réduite. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Les armes de la technique de combat *Lances* n'étaient pas indiquées sur la fiche de personnage. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Les valeurs d'attributs minimum étaient ignorées pour les valeurs de compétence élevées. Le maximum de la valeur de compétence est l'attribut lié le plus élevé + 2, donc l'attribut le plus élevé doit avoir une valeur minimale de VC − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- Les noms des CS *Intuition tactique* et *Détection des embuscades* ont été échangés. [#636](https://github.com/elyukai/optolith-client/issues/636)
|
||||
- Le nouveau domaine d'application *Publications professionnelles* pour le talent *Droit* avait un prérequis de capacité spéciale erronée. [#680](https://github.com/elyukai/optolith-client/issues/680)
|
||||
- Certains éléments de la liste étaient plus larges que la liste actuelle. [#683](https://github.com/elyukai/optolith-client/pull/683)
|
||||
- Les en-têtes de liste n'étaient pas centrés par défaut. [#637](https://github.com/elyukai/optolith-client/issues/637)
|
||||
- Les PV dépensés de façon permanente ne permettaient pas d'acheter des PV supplémentaires. [#606](https://github.com/elyukai/optolith-client/issues/606)
|
||||
- On ne pouvait pas ajouter de PV supplémentaire si la CON est de 8 et que rien n'en dépend. [#694](https://github.com/elyukai/optolith-client/issues/694)
|
||||
- La CS *Maîtrise de l'aspect* n'apparait pas même avec trois liturgies de VC 10 ou plus. [#591](https://github.com/elyukai/optolith-client/issues/591)
|
||||
- La vérification des mises à jour et le bouton de vérification des mises à jour sont maintenant désactivés sur macOS, car les mises à jour ne sont pas possibles sur macOS actuellement. [#589](https://github.com/elyukai/optolith-client/issues/589)
|
||||
- Techniques de combat mal applicables pour la CS *détection des embuscades*. [#658](https://github.com/elyukai/optolith-client/issues/658)
|
||||
- Les alertes de mise à jour automatique étaient activées sur Linux même lorsqu'il n'y avait pas de mises à jour automatiques directes disponibles. [#573](https://github.com/elyukai/optolith-client/issues/573)
|
||||
- Le dialogue de sauvegarde pour l'exportation d'un caractère en JSON n'a pas ajouté l'extension de fichier au nom de fichier suggéré. [#718](https://github.com/elyukai/optolith-client/issues/718)
|
||||
|
||||
### API
|
||||
|
||||
Il y a des changements importants et radicaux à venir avec la prochaine version, donc si vous utilisez la source Optolith ou enregistrez des données, je vous recommande de venir sur [Discord](https://discord.gg/wfdgB9g) afin que je puisse vous dire ce qui va être changé en détail. Puisque de plus en plus de personnes dépendent des données d'Optolith, je vais maintenant suivre strictement le versionnement sémantique afin que vous puissiez vous fier à la compatibilité des fichiers sources en vérifiant simplement le numéro de version. Il y aura un nouveau format pour les héros ainsi que pour les données sources. Vous pouvez également donner votre avis sur les changements, puisqu'ils ne sont pas encore complètement terminés – mais je pense qu'il serait quand même judicieux de vérifier les changements le plus tôt possible. La prochaine version majeure est encore un peu loin, vous aurez donc tout le temps d'ajuster votre logiciel.
|
||||
|
||||
### Remerciements
|
||||
|
||||
Enfin, un grand **merci** aux personnes suivantes sur GitHub qui ont contribué à cette publication !
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.3.2
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- La description de la sélection des techniques de combat pour les variantes de professions n'est pas rendue correctement. #539
|
||||
- Les professions ne tenaient pas compte des conditions prérequises. #507
|
||||
- Le champ de texte "Titre" des données personnelles était mal renseigné. #492
|
||||
- *Religion* avait une VC 6 pour la plupart des professions cléricales. #475
|
||||
- Les États débordaient de la feuille de personnage en PDF mais pas dans Optolith. #473
|
||||
- Le `config.json` a encore provoqué des erreurs pour de nombreux utilisateurs. Le problème devrait être réglé, finalement. #476
|
||||
- Dans de très rares cas, des parties d'un fichier de héros ou la totalité d'un fichier de héros ont été effacées. C'est pour cette raison que la sauvegarde des héros a été complètement réécrite et condensée pour éviter d'autres problèmes. #477
|
||||
- Rajout du dialecte Garethi manquant *Nostrien*. #480
|
||||
- Rectification des licences tierces. #471
|
||||
- Rectification des désavantages avec des options à choisir qui ne pouvaient pas être achetés. #470
|
||||
|
||||
### Known Issues
|
||||
|
||||
- Some of the entries with more complex prerequisites might *display* them wrong but *handle* them correct.
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Bugs rectifiés
|
||||
|
||||
- Dans la version française, vous ne pouviez pas créer de personnages, car vous ne pouviez choisir aucune publication, aucune race, ... #449
|
||||
- Il y a eu une erreur due à des "clés inconnues" dans la configuration. #450
|
||||
- Sur la feuille de personnage, les états n'étaient affichés dans une seule colonne que si le nombre d'états était pair. #448
|
||||
|
||||
## 1.3.0
|
||||
|
||||
Version initiale.
|
||||
@@ -0,0 +1,58 @@
|
||||
## 1.4.2
|
||||
|
||||
### Migliorie
|
||||
|
||||
- Ridotto il tempo di avvio. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Correzioni
|
||||
|
||||
- Le formule della caratteristiche derivate occupano troppo spazio sul foglio d’identità. [#729](https://github.com/elyukai/optolith-client/issues/729)
|
||||
- Rimozione dei requisiti doppi in alcune abilità speciali. [#730](https://github.com/elyukai/optolith-client/issues/730)
|
||||
- Correzioni minori della localizzazione italiana. [#731](https://github.com/elyukai/optolith-client/issues/731)
|
||||
- Alcune tecniche di combattimento possono essere impostate a valori più elevati di quanto ammesso dal regolamento. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- Il margine superiore delle finestre in sovrimpressione, come nell’inserimento dei vantaggi, è troppo stretto. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- Il bonus di impatto non viene calcolato per alcune armi. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- La professione *Mago bianco (Accademia Garetiana di Spada e Bastone)* dal **Regolamento** ha un valore di PAvv sbagliato e *Astronomia 4* invece di *Alchimia 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Campo di testo delle abilità degli animali nel foglio d’identità è troppo piccolo per contenere tutte le abilità. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- L’abilità speciale *Bladestorm* si applica alle tecniche di combattimento sbagliate. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Immagini con estensione in maiuscoletto non vengono accettate come ritratto. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- Viene mostrata l’icona di caricamento invece del ritratto su fogli d’identità di personaggi senza ritratto. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Su Mac, con lo sfondo pergamenato attivato, vengono generate pagine bianche di troppo nel PDF finale. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** a tutti coloro che forniscono commenti, suggerimenti e aiuto sui vari canali sociali e un grazie alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Correzioni
|
||||
|
||||
- La localizzazione italiana non era disponibile.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
Primo rilascio della versione italiana.
|
||||
|
||||
### API
|
||||
|
||||
Ci sono importanti modifiche in questa nuova versione, che cambiano anche il modo in cui sono memorizzati i dati. Se fai uso dei file memorizzati da Optolith, è possibile fare un salto su [Discord](https://discord.gg/wfdgB9g) in modo da avere più informazioni sulle modifiche nel dettaglio.
|
||||
Siccome sempre più persone fanno uso dei dati di Optolith, da questa versione in poi seguiremo le regole del *versionamento semantico*, in modo da permettere agli utenti di fare affidamento alla compatibilità dei file sorgente e dei file dei dati, verificandone soltanto il numero di versione.
|
||||
Ci sarà un nuovo formato di dati sia per gli eroi memorizzati che per i dati sorgente.
|
||||
Le modifiche non sono ancora finalizzate e la nuova versione maggiore è ancora lontana, ma è consigliato aggiornarsi al nuovo formato di dati il prima possibile.
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
@@ -0,0 +1,146 @@
|
||||
## 1.4.2
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
- Start-up was trager dan in 1.3.2. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Gevechtstechnieken maximum GtR kon hoger zijn dan toegelaten. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- The top margin van overlay venters – zoals bij het toevoegen van Voordelen – was te klein. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- De P+T schade bonus werd niet toegevoegd aan wapens zonder extra schade. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- Beroep *Witte Magier (Gareth Academy van Zwaard en Staf)* van de **Basis regels** had een verkeerde AP waarde en *Astronomie 4* in plaats van *Alchemie 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Dier vaardigheids tekst veld op de PDF export was te kort om alle relevante vaardigheden van een dier te bevatten. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- Afbeeldingen met uppercase file extensies werden niet aanvaard asl portret afbeeldingen. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- Het upload icon werd getoont in plaats van de portret afbeelding op het helden document als er geen portret opgeven was. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Op de Mac, als de papier achtergrond aan stond, werden er additionele lege paginas toegevoegd aan de PDF output. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Thank you
|
||||
|
||||
Ten laatste maar zeker niet ten minste een dikke **Dank u** voor iedereen die feedback en help geeft in de verschillende kanalen alsook de volgende mensen op GitHub die bijgedragen hebben tot deze release!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Italiaans was niet beschikbaar.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
### Nieuwe features
|
||||
|
||||
- Italiaanse vertaling [#667](https://github.com/elyukai/optolith-client/issues/667) [#610](https://github.com/elyukai/optolith-client/issues/610)
|
||||
- Optionele papier-stijl achtergrond voor helden document. [#36](https://github.com/elyukai/optolith-client/issues/36)
|
||||
- helden document zoom optie. [#665](https://github.com/elyukai/optolith-client/issues/665)
|
||||
- Geld toevoegen/aftrekken. [#666](https://github.com/elyukai/optolith-client/issues/666)
|
||||
- Voeg Voordelen, Nadelen en Competenties op maat toe met gekozen naam en AP waarde (tijdelijke oplossing) [#632](https://github.com/elyukai/optolith-client/issues/632)
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
- Gewicht wordt nu in zijn eigen kolom getoond in de uitrusting tab. [#305](https://github.com/elyukai/optolith-client/issues/305)
|
||||
- Onnodige gevechtsdocumenten worden niet meer gebruikt, het hangt nu af van welke wapenrusting de held heeft. Het gaat standaard naar de normale wapenrusting als er geen wapenrusting aanwezig is. [#407](https://github.com/elyukai/optolith-client/issues/407)
|
||||
- Je kan nu ook klikken op namen om de regeltekst te zien. [#556](https://github.com/elyukai/optolith-client/issues/556)
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Varianten van het beroep *Gewijde van Boron* had verkeerde talent aanpassingen. [#548](https://github.com/elyukai/optolith-client/issues/548)
|
||||
- Beroep *Witte Tovenaar (Zwaard & Staf)* had een verkeerde AP waarde and een extra talent aanpassing. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Vaste veelvoorkomende beroepen van een kultuur zullen nu getoond worden zelfs al zijn de ze niet van de **Basis Regels**. [#563](https://github.com/elyukai/optolith-client/issues/563)
|
||||
- The interface kon kleiner gemaakt worden. [#487](https://github.com/elyukai/optolith-client/issues/487)
|
||||
- Wapens van de gevechtscompetentie *Lans* werden niet getoond op het helden document. [#662](https://github.com/elyukai/optolith-client/issues/662)
|
||||
- Goede Eigenschappen negeerden het minimum veroorzaakt door hoge talent waarden. De maximum van een talent waarde is het hoogste gerelateerde goede eigenschap + 2, dus een goede eigenschap moet een minimum waarde hebben van de hoogste talent waarde  − 2. [#630](https://github.com/elyukai/optolith-client/issues/630)
|
||||
- Het nieuwe toepassingsgebeird *Professionele Publicaties* voor het talent *Wet* had een verkeerde Competentie vereiste. [#680](https://github.com/elyukai/optolith-client/issues/680)
|
||||
- Sommige lijst elementen werden breder dan de eigenlijke lijst. [#683](https://github.com/elyukai/optolith-client/pull/683)
|
||||
- Lijst hoofdingen werden niet automatisch gecentreerd. [#637](https://github.com/elyukai/optolith-client/issues/637)
|
||||
- Permantent gespendeerde LP lieten niet toe om extra LP te kopen. [#606](https://github.com/elyukai/optolith-client/issues/606)
|
||||
- Extra LP konden niet worden gekocht als CO 8 was en er niets van afhing. [#694](https://github.com/elyukai/optolith-client/issues/694)
|
||||
- Competentie *Aspect Kennis* verscheen niet, zelfs met drie liturgieen op talent waarde 10 of hoger. [#591](https://github.com/elyukai/optolith-client/issues/591)
|
||||
- Tovertruuk Heksenblik kon worden geselecteerd door alle Tovenaars. [#377](https://github.com/elyukai/optolith-client/issues/377)
|
||||
- Updater tests en de test voor de update knop zijn nu uitgeschakeld op MacOS want updates zijn momenteel niet mogelijk op MacOS. [#589](https://github.com/elyukai/optolith-client/issues/589)
|
||||
- Verkeerde toegepaste gevechtscompetentie voor Competentie Vijand Waarnemen. [#658](https://github.com/elyukai/optolith-client/issues/658)
|
||||
- Auto-update waarschuwingen werden aangezet op Linux, zelfs al was er geen direct auto-update beschikbaar. [#573](https://github.com/elyukai/optolith-client/issues/573)
|
||||
- Het dialoogvenster voor het exporteren van een teken, omdat JSON de bestandsextensie niet heeft toegevoegd aan de voorgestelde bestandsnaam. [#718](https://github.com/elyukai/optolith-client/issues/718)
|
||||
|
||||
### API
|
||||
|
||||
Er komen belangijke en brekende veranderingen aan met de volgende release, dus als je Optolith source of data gebruikt, raad ik je aan om naar de [Discord](https://discord.gg/wfdgB9g) te komen zodat ik je in detail kan uitleggen wat er gaat veranderen. Vermits meer en meer mensen afhankelijk zijn van Optolith's data zal ik nu strict versiebeheer volgen zodat je op de compatibiliteit van de sources kan rekenen door het versie nummer te controleren. Er zal een nieuw formaat komen voor de helden en de bron data. Je kan ook feedback geven op de veranderingen, vermits ze nog niet *helemaal* volledig zijn – maar ik denk dat het zinnig zou zijn om de verandering zo snel mogelijk na te kijken. De volgende versie duurt nog wel even, zodat je voldoende tijd hebt om je software aan te passen.
|
||||
|
||||
### Thank you
|
||||
|
||||
Tenslotte, maar zeker niet tenminste, een dikke **Dankuwel** voor de volgende mensen op GitHub die bijgedragen hebben tot deze release!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.3.2
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- Beschrijving van gevechtstechnieken selectie voor beroepsvarianten wordt niet correct getoond. #539
|
||||
- Beroepen reageerden niet correct op vereisten. #507
|
||||
- Het tekstveld "Titel" in de persoonlijk data had een verkeerd label. #492
|
||||
- *Goden & Kulten* was 6 voor de meeste Gewijde beroepen. #475
|
||||
- Statussen waren te lang op je helden dokument PDF maar niet in Optolith. #473
|
||||
- De `config.json` had nog altijd fouten voor veel gebruikers. Het probleem zou eindelijk opgelost moeten zijn. #476
|
||||
- In hele zeldzame situaties werd de helden file geheel of gedeeltelijk verwijderd. Het saven van helden is geheel herschreven en compacter gemaakt om verdere problemen te voorkomen. #477
|
||||
- Voeg het ontbrekende Garethi dialect *Nostrian* toe. #480
|
||||
- Vereisten van de Competentie Energie Controle zijn aangepast. #471
|
||||
- Derde parij licenties zijn aangepast. #471
|
||||
|
||||
### Bekende problemen
|
||||
|
||||
- Sommige elementen met complexe vereisten worden verkeerd getoont maar wel correct behandeld.
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- In de franse versie kon je geen helden maken, want je kon geen publicaties, ras, ... kiezen #449
|
||||
- Er werd een error gemeld voor "unknown keys" in de config. #450
|
||||
- Op je helden document werden staten enkel getoond in een enkele kolom als het aantal staten even was. #448
|
||||
|
||||
## 1.3.0
|
||||
|
||||
### Nieuwe features
|
||||
|
||||
- Franse versie (incl. Regelwerk) #34
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
- Het zoekveld in de Wiki wordt niet langer gewist als men van categorie wisselt. #286
|
||||
- Als het importeren van een held mislukt wordt nu gespecifieerd wat het probleem was (incompatibele versie, onbekend bestand, enz.) #343 #394
|
||||
- Volledig nieuwe databankstructuur. Daardoor 50% snellere laadtijd.
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
- De taaltabel vervormde de lay-out als Hjaldings/Saga-Thorwals actief was. #419
|
||||
- De teksten bij de persoonlijke data op het heldendocument werden gedeeltelijk beknot. #413
|
||||
- Het totale gewicht is nu gealigneerd op de het uitrustingsdocument. #291
|
||||
- Een voorwerp kon niet worden opgenomen als geïmproviseerd wapen. #290
|
||||
- De infotabel voor routineproeven was niet correct gealigneerd. #214
|
||||
- Angst voor: keuze/eigen tekst werd niet getoond op het heldendocument. #213
|
||||
- Men kon geen prenten importeren van een server als karaktertekening. #229
|
||||
- Het toevoegen van AP werkte niet als men het invoerveld niet verliet. #341
|
||||
- Wapens die geen schade veroorzaken, tonen niet langer enkel D als schade. #353
|
||||
- Geen talentvermindering op *Spoorzoeken* meer mogelijk bij jager (Premiejager). #357
|
||||
- Com *Uithaal* toonde GtW Zwaarden dubbel. #355
|
||||
- Een geëxporteerde held met een BOM kon niet worden gelezen. #37
|
||||
- Veranderingen aan voorwerpen konden onder bepaalde omstandigheden niet worden bewaard. #389
|
||||
- Fout opgelost waarbij het veld "waar gedragen" niet bewaard werd bij voorwerpen. #369
|
||||
- De maximale waarde voor gevechtstechnieken door de hoofdeigenschap werkt weer. #379
|
||||
- Typo in ATTRIBUTO (Lichaamskracht), tweede uitbreiding verbeterd. #366
|
||||
- De toepassingsconfiguratie veroorzaakt geen fouten meer. #358
|
||||
|
||||
## 1.2.0
|
||||
|
||||
Eerste Nederlandstalige versie.
|
||||
+2
-13
@@ -1,25 +1,14 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @typedef {import('ts-jest')}
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
||||
testRegex: "(/__tests__/.*|(\\.|/)(test|spec)|_(test|spec)\\.bs)\\.jsx?$",
|
||||
moduleFileExtensions: [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
testEnvironment: "node",
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsConfig: "tsconfig.test.json"
|
||||
}
|
||||
}
|
||||
testEnvironment: "node"
|
||||
}
|
||||
|
||||
Generated
+19585
-7673
File diff suppressed because it is too large
Load Diff
+68
-61
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "optolith",
|
||||
"productName": "Optolith",
|
||||
"version": "1.4.0-alpha.13",
|
||||
"productName": "Optolith Insider",
|
||||
"version": "1.4.3-alpha.0",
|
||||
"description": "A generator for heroes for the P&P-RPG 'The Dark Eye'.",
|
||||
"author": {
|
||||
"name": "Lukas Obermann",
|
||||
@@ -9,66 +9,62 @@
|
||||
},
|
||||
"homepage": "https://github.com/elyukai/optolith-client",
|
||||
"license": "MPL-2.0",
|
||||
"main": "app/index.js",
|
||||
"main": "src/Main.bs.js",
|
||||
"dependencies": {
|
||||
"@glennsl/bs-json": "5.0.2",
|
||||
"ajv": "6.12.0",
|
||||
"bs-platform": "7.3.2",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron-log": "4.1.2",
|
||||
"electron-updater": "4.3.1",
|
||||
"electron-window-state": "5.0.3",
|
||||
"esm": "^3.2.25",
|
||||
"moment": "2.25.3",
|
||||
"mousetrap": "1.6.5",
|
||||
"react": "16.13.1",
|
||||
"react-custom-scrollbars": "4.2.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-markdown": "4.3.1",
|
||||
"react-portal": "4.2.1",
|
||||
"react-redux": "7.2.0",
|
||||
"react-textfit": "1.1.0",
|
||||
"reason-react": "0.8.0",
|
||||
"reductive": "2.0.1",
|
||||
"redux": "4.0.5",
|
||||
"@glennsl/bs-jest": "^0.7.0",
|
||||
"@glennsl/bs-json": "^5.0.2",
|
||||
"@rescript/react": "^0.10.3",
|
||||
"bs-decoders": "^0.6.0",
|
||||
"electron": "^13.1.7",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-log": "^4.2.4",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"moment": "^2.25.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"react": "^17.0.2",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^6.0.2",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-textfit": "^1.1.1",
|
||||
"reason-react": "^0.9.1",
|
||||
"reductive": "^2.1.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"redux-thunk": "2.3.0",
|
||||
"remark-breaks": "1.0.5",
|
||||
"reselect": "4.0.0",
|
||||
"semver": "7.3.2",
|
||||
"ssh2-sftp-client": "5.1.2",
|
||||
"tslib": "1.11.2",
|
||||
"yaml": "1.9.2"
|
||||
"redux-thunk": "^2.3.0",
|
||||
"remark-breaks": "^1.0.5",
|
||||
"rescript": "^9.1.4",
|
||||
"reselect": "^4.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"ssh2-sftp-client": "^5.3.1",
|
||||
"yaml": "^1.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@glennsl/bs-jest": "0.5.1",
|
||||
"@types/classnames": "2.2.10",
|
||||
"@types/jest": "25.2.1",
|
||||
"@types/mousetrap": "1.6.3",
|
||||
"@types/node": "13.13.5",
|
||||
"@types/react": "16.9.34",
|
||||
"@types/react-custom-scrollbars": "4.0.6",
|
||||
"@types/react-dom": "16.9.7",
|
||||
"@types/react-portal": "4.0.2",
|
||||
"@types/react-redux": "7.1.8",
|
||||
"@types/semver": "7.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "2.31.0",
|
||||
"@typescript-eslint/parser": "2.31.0",
|
||||
"custom-env": "2.0.1",
|
||||
"dotenv": "8.2.0",
|
||||
"electron": "8.2.5",
|
||||
"electron-builder": "22.8.0",
|
||||
"electron-notarize": "0.3.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-plugin-react": "7.19.0",
|
||||
"eslint-plugin-react-hooks": "4.0.0",
|
||||
"fs-extra": "9.0.0",
|
||||
"gentype": "3.22.0",
|
||||
"jest": "26.4.2",
|
||||
"@commitlint/cli": "^12.1.1",
|
||||
"@commitlint/config-conventional": "^12.1.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/mousetrap": "^1.6.7",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-portal": "^4.0.2",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/semver": "^7.3.4",
|
||||
"electron-builder": "^22.10.5",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.3",
|
||||
"qusly-core": "2.5.2",
|
||||
"sass": "1.26.5",
|
||||
"ts-jest": "26.3.0",
|
||||
"typescript": "3.8.3"
|
||||
"sass": "^1.32.11",
|
||||
"standard-version": "^9.2.0",
|
||||
"webpack": "^5.35.1",
|
||||
"webpack-cli": "^4.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prerelease:win": "set NODE_ENV=development && node ./deploy/buildInsiderWin.js",
|
||||
@@ -77,16 +73,27 @@
|
||||
"stable:win": "set NODE_ENV=production && node ./deploy/buildWin.js",
|
||||
"stable:linux": "NODE_ENV=production node ./deploy/buildLinux.js",
|
||||
"stable:mac": "NODE_ENV=production node ./deploy/buildMac.js",
|
||||
"css:build": "node --experimental-modules ./deploy/buildSass.mjs",
|
||||
"css:build": "sass --style=compressed src/Main.scss dist/main.css",
|
||||
"css:watch": "sass --watch --style=compressed src/Main.scss dist/main.css",
|
||||
"build": "npm run re:build && npm run css:build && npm run js:build",
|
||||
"js:build": "webpack --config ./webpack.config.js",
|
||||
"js:watch": "webpack -w --config ./webpack.config.js",
|
||||
"ts:build": "tsc",
|
||||
"ts:watch": "tsc --watch",
|
||||
"ts:watch": "tsc -w",
|
||||
"re:build": "bsb -make-world",
|
||||
"re:watch": "bsb -make-world -w",
|
||||
"re:clean": "bsb -clean-world",
|
||||
"lint": "eslint \"src/\" --ext \".ts\" --ext \".tsx\"",
|
||||
"start": "electron .",
|
||||
"test": "jest",
|
||||
"circ": "madge -c ./src"
|
||||
"circ": "madge -c ./src",
|
||||
"release:main": "standard-version --skip.commit --skip.tag",
|
||||
"release:pre": "standard-version --prerelease=alpha --skip.changelog"
|
||||
},
|
||||
"repository": "github:elyukai/optolith-client"
|
||||
"repository": "github:elyukai/optolith-client",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
type action =
|
||||
| SetLocale(Either.t(string, string))
|
||||
| SetLocale(result(string, string))
|
||||
| SwitchActiveItemHints
|
||||
| SetTheme(FilterOptions.theme)
|
||||
| SwitchEditingHeroAfterCreationPhase
|
||||
@@ -32,7 +32,7 @@ type action =
|
||||
| SetCultureAreaKnowledge(string)
|
||||
| AddAdventurePoints(int)
|
||||
| EndHeroCreation
|
||||
| SetPactCategory(Maybe.t(int))
|
||||
| SetPactCategory(option(int))
|
||||
| SetPactLevel(int)
|
||||
| SetPactTargetType(int)
|
||||
| SetTargetDomain(int)
|
||||
@@ -0,0 +1,199 @@
|
||||
type action =
|
||||
| AddAttributePoint(int)
|
||||
| RemoveAttributePoint(int)
|
||||
| AddLifePoint
|
||||
| RemoveLifePoint
|
||||
| AddArcaneEnergyPoint
|
||||
| RemoveArcaneEnergyPoint
|
||||
| AddKarmaPoint
|
||||
| RemoveKarmaPoint
|
||||
| AddLostLifePoint
|
||||
| RemoveLostLifePoint
|
||||
| AddLostLifePoints(int)
|
||||
| AddBoughtBackArcaneEnergyPoint
|
||||
| RemoveBoughtBackArcaneEnergyPoint
|
||||
| AddLostArcaneEnergyPoint
|
||||
| RemoveLostArcaneEnergyPoint
|
||||
| AddLostArcaneEnergyPoints(int)
|
||||
| AddBoughtBackKarmaPoint
|
||||
| RemoveBoughtBackKarmaPoint
|
||||
| AddLostKarmaPoint
|
||||
| RemoveLostKarmaPoint
|
||||
| AddLostKarmaPoints(int)
|
||||
| SetAttributeAdjustment(int);
|
||||
|
||||
// export const addAttributePoint = (id: string): ReduxAction<Promise<void>> =>
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const wiki_attributes = getWikiAttributes (state)
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
// const mhero_attributes = fmapF (mhero) (HeroModel.A.attributes)
|
||||
//
|
||||
// const missingAPForInc =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (getAreSufficientAPAvailableForIncrease (getIsInCharacterCreation (state))
|
||||
// (bind (mhero_attributes)
|
||||
// (lookup (id))))
|
||||
// (lookup (id) (wiki_attributes)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAPForInc)) {
|
||||
// dispatch<AddAttributePointAction> ({
|
||||
// type: ActionTypes.ADD_ATTRIBUTE_POINT,
|
||||
// payload: {
|
||||
// id,
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAPForInc)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addLifePoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedLifePoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddLifePointAction> ({
|
||||
// type: ActionTypes.ADD_LIFE_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addArcaneEnergyPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedArcaneEnergyPoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddArcaneEnergyPointAction> ({
|
||||
// type: ActionTypes.ADD_ARCANE_ENERGY_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addKarmaPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedKarmaPoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddKarmaPointAction> ({
|
||||
// type: ActionTypes.ADD_KARMA_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addBoughtBackAEPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// bindF (getMissingAP (getIsInCharacterCreation (state))
|
||||
// (2))
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddBoughtBackAEPointAction> ({
|
||||
// type: ActionTypes.ADD_BOUGHT_BACK_AE_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addBoughtBackKPPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// bindF (getMissingAP (getIsInCharacterCreation (state))
|
||||
// (2))
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddBoughtBackKPPointAction> ({
|
||||
// type: ActionTypes.ADD_BOUGHT_BACK_KP_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,5 @@
|
||||
type action =
|
||||
| SetCulture(int)
|
||||
| SetCulturesSortOrder(FilterOptions.culturesSortOptions)
|
||||
| SetCulturesVisibilityFilter(FilterOptions.culturesVisibilityFilter)
|
||||
| SetCulturesFilterText(string);
|
||||
@@ -0,0 +1,50 @@
|
||||
type t =
|
||||
| Advantage of Advantage.Static.t
|
||||
| Disadvantage of Disadvantage.Static.t
|
||||
| GeneralSpecialAbility of GeneralSpecialAbility.Static.t
|
||||
| FatePointSpecialAbility of FatePointSpecialAbility.Static.t
|
||||
| CombatSpecialAbility of CombatSpecialAbility.Static.t
|
||||
| MagicalSpecialAbility of MagicalSpecialAbility.Static.t
|
||||
| StaffEnchantment of StaffEnchantment.Static.t
|
||||
| FamiliarSpecialAbility of FamiliarSpecialAbility.Static.t
|
||||
| KarmaSpecialAbility of KarmaSpecialAbility.Static.t
|
||||
| ProtectiveWardingCircleSpecialAbility of
|
||||
ProtectiveWardingCircleSpecialAbility.Static.t
|
||||
| CombatStyleSpecialAbility of CombatStyleSpecialAbility.Static.t
|
||||
| AdvancedCombatSpecialAbility of AdvancedCombatSpecialAbility.Static.t
|
||||
| CommandSpecialAbility of CommandSpecialAbility.Static.t
|
||||
| MagicStyleSpecialAbility of MagicStyleSpecialAbility.Static.t
|
||||
| AdvancedMagicalSpecialAbility of AdvancedMagicalSpecialAbility.Static.t
|
||||
| SpellSwordEnchantment of SpellSwordEnchantment.Static.t
|
||||
| DaggerRitual of DaggerRitual.Static.t
|
||||
| InstrumentEnchantment of InstrumentEnchantment.Static.t
|
||||
| AttireEnchantment of AttireEnchantment.Static.t
|
||||
| OrbEnchantment of OrbEnchantment.Static.t
|
||||
| WandEnchantment of WandEnchantment.Static.t
|
||||
| BrawlingSpecialAbility of BrawlingSpecialAbility.Static.t
|
||||
| AncestorGlyph of AncestorGlyph.Static.t
|
||||
| CeremonialItemSpecialAbility of CeremonialItemSpecialAbility.Static.t
|
||||
| Sermon of Sermon.Static.t
|
||||
| LiturgicalStyleSpecialAbility of LiturgicalStyleSpecialAbility.Static.t
|
||||
| AdvancedKarmaSpecialAbility of AdvancedKarmaSpecialAbility.Static.t
|
||||
| Vision of Vision.Static.t
|
||||
| MagicalTradition of MagicalTradition.Static.t
|
||||
| BlessedTradition of BlessedTradition.Static.t
|
||||
| Paktgeschenk of Paktgeschenk.Static.t
|
||||
| SikaryanRaubSonderfertigkeit of SikaryanRaubSonderfertigkeit.Static.t
|
||||
| LykanthropischeGabe of LykanthropischeGabe.Static.t
|
||||
| Talentstilsonderfertigkeit of Talentstilsonderfertigkeit.Static.t
|
||||
| ErweiterteTalentsonderfertigkeit of
|
||||
ErweiterteTalentsonderfertigkeit.Static.t
|
||||
| Kugelzauber of Kugelzauber.Static.t
|
||||
| Kesselzauber of Kesselzauber.Static.t
|
||||
| Kappenzauber of Kappenzauber.Static.t
|
||||
| Spielzeugzauber of Spielzeugzauber.Static.t
|
||||
| Schalenzauber of Schalenzauber.Static.t
|
||||
| SexSchicksalspunkteSonderfertigkeit of
|
||||
SexSchicksalspunkteSonderfertigkeit.Static.t
|
||||
| SexSonderfertigkeit of SexSonderfertigkeit.Static.t
|
||||
| Waffenzauber of Waffenzauber.Static.t
|
||||
| Sichelritual of Sichelritual.Static.t
|
||||
| Ringzauber of Ringzauber.Static.t
|
||||
| Chronikzauber of Chronikzauber.Static.t
|
||||
@@ -0,0 +1,349 @@
|
||||
let isActive (x : 'a Activatable_Dynamic.t) = Ley_List.Extra.notNull x.active
|
||||
|
||||
let isActiveM x = Ley_Option.option false isActive x
|
||||
|
||||
let id = function
|
||||
| Activatable.Advantage x -> Id.Activatable.Advantage x.id
|
||||
| Disadvantage x -> Disadvantage x.id
|
||||
| GeneralSpecialAbility x -> GeneralSpecialAbility x.id
|
||||
| FatePointSpecialAbility x -> FatePointSpecialAbility x.id
|
||||
| CombatSpecialAbility x -> CombatSpecialAbility x.id
|
||||
| MagicalSpecialAbility x -> MagicalSpecialAbility x.id
|
||||
| StaffEnchantment x -> StaffEnchantment x.id
|
||||
| FamiliarSpecialAbility x -> FamiliarSpecialAbility x.id
|
||||
| KarmaSpecialAbility x -> KarmaSpecialAbility x.id
|
||||
| ProtectiveWardingCircleSpecialAbility x ->
|
||||
ProtectiveWardingCircleSpecialAbility x.id
|
||||
| CombatStyleSpecialAbility x -> CombatStyleSpecialAbility x.id
|
||||
| AdvancedCombatSpecialAbility x -> AdvancedCombatSpecialAbility x.id
|
||||
| CommandSpecialAbility x -> CommandSpecialAbility x.id
|
||||
| MagicStyleSpecialAbility x -> MagicStyleSpecialAbility x.id
|
||||
| AdvancedMagicalSpecialAbility x -> AdvancedMagicalSpecialAbility x.id
|
||||
| SpellSwordEnchantment x -> SpellSwordEnchantment x.id
|
||||
| DaggerRitual x -> DaggerRitual x.id
|
||||
| InstrumentEnchantment x -> InstrumentEnchantment x.id
|
||||
| AttireEnchantment x -> AttireEnchantment x.id
|
||||
| OrbEnchantment x -> OrbEnchantment x.id
|
||||
| WandEnchantment x -> WandEnchantment x.id
|
||||
| BrawlingSpecialAbility x -> BrawlingSpecialAbility x.id
|
||||
| AncestorGlyph x -> AncestorGlyph x.id
|
||||
| CeremonialItemSpecialAbility x -> CeremonialItemSpecialAbility x.id
|
||||
| Sermon x -> Sermon x.id
|
||||
| LiturgicalStyleSpecialAbility x -> LiturgicalStyleSpecialAbility x.id
|
||||
| AdvancedKarmaSpecialAbility x -> AdvancedKarmaSpecialAbility x.id
|
||||
| Vision x -> Vision x.id
|
||||
| MagicalTradition x -> MagicalTradition x.id
|
||||
| BlessedTradition x -> BlessedTradition x.id
|
||||
| Paktgeschenk x -> Paktgeschenk x.id
|
||||
| SikaryanRaubSonderfertigkeit x -> SikaryanRaubSonderfertigkeit x.id
|
||||
| LykanthropischeGabe x -> LykanthropischeGabe x.id
|
||||
| Talentstilsonderfertigkeit x -> Talentstilsonderfertigkeit x.id
|
||||
| ErweiterteTalentsonderfertigkeit x -> ErweiterteTalentsonderfertigkeit x.id
|
||||
| Kugelzauber x -> Kugelzauber x.id
|
||||
| Kesselzauber x -> Kesselzauber x.id
|
||||
| Kappenzauber x -> Kappenzauber x.id
|
||||
| Spielzeugzauber x -> Spielzeugzauber x.id
|
||||
| Schalenzauber x -> Schalenzauber x.id
|
||||
| SexSchicksalspunkteSonderfertigkeit x ->
|
||||
SexSchicksalspunkteSonderfertigkeit x.id
|
||||
| SexSonderfertigkeit x -> SexSonderfertigkeit x.id
|
||||
| Waffenzauber x -> Waffenzauber x.id
|
||||
| Sichelritual x -> Sichelritual x.id
|
||||
| Ringzauber x -> Ringzauber x.id
|
||||
| Chronikzauber x -> Chronikzauber x.id
|
||||
|
||||
let id' = function
|
||||
| Activatable.Advantage x -> x.id
|
||||
| Disadvantage x -> x.id
|
||||
| GeneralSpecialAbility x -> x.id
|
||||
| FatePointSpecialAbility x -> x.id
|
||||
| CombatSpecialAbility x -> x.id
|
||||
| MagicalSpecialAbility x -> x.id
|
||||
| StaffEnchantment x -> x.id
|
||||
| FamiliarSpecialAbility x -> x.id
|
||||
| KarmaSpecialAbility x -> x.id
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.id
|
||||
| CombatStyleSpecialAbility x -> x.id
|
||||
| AdvancedCombatSpecialAbility x -> x.id
|
||||
| CommandSpecialAbility x -> x.id
|
||||
| MagicStyleSpecialAbility x -> x.id
|
||||
| AdvancedMagicalSpecialAbility x -> x.id
|
||||
| SpellSwordEnchantment x -> x.id
|
||||
| DaggerRitual x -> x.id
|
||||
| InstrumentEnchantment x -> x.id
|
||||
| AttireEnchantment x -> x.id
|
||||
| OrbEnchantment x -> x.id
|
||||
| WandEnchantment x -> x.id
|
||||
| BrawlingSpecialAbility x -> x.id
|
||||
| AncestorGlyph x -> x.id
|
||||
| CeremonialItemSpecialAbility x -> x.id
|
||||
| Sermon x -> x.id
|
||||
| LiturgicalStyleSpecialAbility x -> x.id
|
||||
| AdvancedKarmaSpecialAbility x -> x.id
|
||||
| Vision x -> x.id
|
||||
| MagicalTradition x -> x.id
|
||||
| BlessedTradition x -> x.id
|
||||
| Paktgeschenk x -> x.id
|
||||
| SikaryanRaubSonderfertigkeit x -> x.id
|
||||
| LykanthropischeGabe x -> x.id
|
||||
| Talentstilsonderfertigkeit x -> x.id
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.id
|
||||
| Kugelzauber x -> x.id
|
||||
| Kesselzauber x -> x.id
|
||||
| Kappenzauber x -> x.id
|
||||
| Spielzeugzauber x -> x.id
|
||||
| Schalenzauber x -> x.id
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.id
|
||||
| SexSonderfertigkeit x -> x.id
|
||||
| Waffenzauber x -> x.id
|
||||
| Sichelritual x -> x.id
|
||||
| Ringzauber x -> x.id
|
||||
| Chronikzauber x -> x.id
|
||||
|
||||
let idDeepVariant x = x |> id |> Id.Activatable.to_nested
|
||||
|
||||
let name = function
|
||||
| Activatable.Advantage x -> x.name
|
||||
| Disadvantage x -> x.name
|
||||
| GeneralSpecialAbility x -> x.name
|
||||
| FatePointSpecialAbility x -> x.name
|
||||
| CombatSpecialAbility x -> x.name
|
||||
| MagicalSpecialAbility x -> x.name
|
||||
| StaffEnchantment x -> x.name
|
||||
| FamiliarSpecialAbility x -> x.name
|
||||
| KarmaSpecialAbility x -> x.name
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.name
|
||||
| CombatStyleSpecialAbility x -> x.name
|
||||
| AdvancedCombatSpecialAbility x -> x.name
|
||||
| CommandSpecialAbility x -> x.name
|
||||
| MagicStyleSpecialAbility x -> x.name
|
||||
| AdvancedMagicalSpecialAbility x -> x.name
|
||||
| SpellSwordEnchantment x -> x.name
|
||||
| DaggerRitual x -> x.name
|
||||
| InstrumentEnchantment x -> x.name
|
||||
| AttireEnchantment x -> x.name
|
||||
| OrbEnchantment x -> x.name
|
||||
| WandEnchantment x -> x.name
|
||||
| BrawlingSpecialAbility x -> x.name
|
||||
| AncestorGlyph x -> x.name
|
||||
| CeremonialItemSpecialAbility x -> x.name
|
||||
| Sermon x -> x.name
|
||||
| LiturgicalStyleSpecialAbility x -> x.name
|
||||
| AdvancedKarmaSpecialAbility x -> x.name
|
||||
| Vision x -> x.name
|
||||
| MagicalTradition x -> x.name
|
||||
| BlessedTradition x -> x.name
|
||||
| Paktgeschenk x -> x.name
|
||||
| SikaryanRaubSonderfertigkeit x -> x.name
|
||||
| LykanthropischeGabe x -> x.name
|
||||
| Talentstilsonderfertigkeit x -> x.name
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.name
|
||||
| Kugelzauber x -> x.name
|
||||
| Kesselzauber x -> x.name
|
||||
| Kappenzauber x -> x.name
|
||||
| Spielzeugzauber x -> x.name
|
||||
| Schalenzauber x -> x.name
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.name
|
||||
| SexSonderfertigkeit x -> x.name
|
||||
| Waffenzauber x -> x.name
|
||||
| Sichelritual x -> x.name
|
||||
| Ringzauber x -> x.name
|
||||
| Chronikzauber x -> x.name
|
||||
|
||||
let selectOptions = function
|
||||
| Activatable.Advantage x -> x.selectOptions
|
||||
| Disadvantage x -> x.selectOptions
|
||||
| GeneralSpecialAbility x -> x.selectOptions
|
||||
| FatePointSpecialAbility x -> x.selectOptions
|
||||
| CombatSpecialAbility x -> x.selectOptions
|
||||
| MagicalSpecialAbility x -> x.selectOptions
|
||||
| StaffEnchantment x -> x.selectOptions
|
||||
| FamiliarSpecialAbility x -> x.selectOptions
|
||||
| KarmaSpecialAbility x -> x.selectOptions
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.selectOptions
|
||||
| CombatStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedCombatSpecialAbility x -> x.selectOptions
|
||||
| CommandSpecialAbility x -> x.selectOptions
|
||||
| MagicStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedMagicalSpecialAbility x -> x.selectOptions
|
||||
| SpellSwordEnchantment x -> x.selectOptions
|
||||
| DaggerRitual x -> x.selectOptions
|
||||
| InstrumentEnchantment x -> x.selectOptions
|
||||
| AttireEnchantment x -> x.selectOptions
|
||||
| OrbEnchantment x -> x.selectOptions
|
||||
| WandEnchantment x -> x.selectOptions
|
||||
| BrawlingSpecialAbility x -> x.selectOptions
|
||||
| AncestorGlyph x -> x.selectOptions
|
||||
| CeremonialItemSpecialAbility x -> x.selectOptions
|
||||
| Sermon x -> x.selectOptions
|
||||
| LiturgicalStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedKarmaSpecialAbility x -> x.selectOptions
|
||||
| Vision x -> x.selectOptions
|
||||
| MagicalTradition x -> x.selectOptions
|
||||
| BlessedTradition x -> x.selectOptions
|
||||
| Paktgeschenk x -> x.selectOptions
|
||||
| SikaryanRaubSonderfertigkeit x -> x.selectOptions
|
||||
| LykanthropischeGabe x -> x.selectOptions
|
||||
| Talentstilsonderfertigkeit x -> x.selectOptions
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.selectOptions
|
||||
| Kugelzauber x -> x.selectOptions
|
||||
| Kesselzauber x -> x.selectOptions
|
||||
| Kappenzauber x -> x.selectOptions
|
||||
| Spielzeugzauber x -> x.selectOptions
|
||||
| Schalenzauber x -> x.selectOptions
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.selectOptions
|
||||
| SexSonderfertigkeit x -> x.selectOptions
|
||||
| Waffenzauber x -> x.selectOptions
|
||||
| Sichelritual x -> x.selectOptions
|
||||
| Ringzauber x -> x.selectOptions
|
||||
| Chronikzauber x -> x.selectOptions
|
||||
|
||||
let input = function
|
||||
| Activatable.Advantage x -> x.input
|
||||
| Disadvantage x -> x.input
|
||||
| GeneralSpecialAbility x -> x.input
|
||||
| FatePointSpecialAbility x -> x.input
|
||||
| CombatSpecialAbility x -> x.input
|
||||
| MagicalSpecialAbility x -> x.input
|
||||
| StaffEnchantment x -> x.input
|
||||
| FamiliarSpecialAbility x -> x.input
|
||||
| KarmaSpecialAbility x -> x.input
|
||||
| ProtectiveWardingCircleSpecialAbility _ -> None
|
||||
| CombatStyleSpecialAbility x -> x.input
|
||||
| AdvancedCombatSpecialAbility x -> x.input
|
||||
| CommandSpecialAbility x -> x.input
|
||||
| MagicStyleSpecialAbility x -> x.input
|
||||
| AdvancedMagicalSpecialAbility x -> x.input
|
||||
| SpellSwordEnchantment x -> x.input
|
||||
| DaggerRitual x -> x.input
|
||||
| InstrumentEnchantment x -> x.input
|
||||
| AttireEnchantment x -> x.input
|
||||
| OrbEnchantment x -> x.input
|
||||
| WandEnchantment x -> x.input
|
||||
| BrawlingSpecialAbility x -> x.input
|
||||
| AncestorGlyph x -> x.input
|
||||
| CeremonialItemSpecialAbility x -> x.input
|
||||
| Sermon x -> x.input
|
||||
| LiturgicalStyleSpecialAbility x -> x.input
|
||||
| AdvancedKarmaSpecialAbility x -> x.input
|
||||
| Vision x -> x.input
|
||||
| MagicalTradition x -> x.input
|
||||
| BlessedTradition x -> x.input
|
||||
| Paktgeschenk x -> x.input
|
||||
| SikaryanRaubSonderfertigkeit x -> x.input
|
||||
| LykanthropischeGabe x -> x.input
|
||||
| Talentstilsonderfertigkeit x -> x.input
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.input
|
||||
| Kugelzauber x -> x.input
|
||||
| Kesselzauber x -> x.input
|
||||
| Kappenzauber x -> x.input
|
||||
| Spielzeugzauber x -> x.input
|
||||
| Schalenzauber x -> x.input
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.input
|
||||
| SexSonderfertigkeit x -> x.input
|
||||
| Waffenzauber x -> x.input
|
||||
| Sichelritual x -> x.input
|
||||
| Ringzauber x -> x.input
|
||||
| Chronikzauber x -> x.input
|
||||
|
||||
let apValue = function
|
||||
| Activatable.Advantage x -> x.apValue
|
||||
| Disadvantage x -> x.apValue
|
||||
| GeneralSpecialAbility x -> x.apValue
|
||||
| FatePointSpecialAbility x -> x.apValue
|
||||
| CombatSpecialAbility x -> x.apValue
|
||||
| MagicalSpecialAbility x -> x.apValue
|
||||
| StaffEnchantment x -> x.apValue
|
||||
| FamiliarSpecialAbility x -> x.apValue
|
||||
| KarmaSpecialAbility x -> x.apValue
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.apValue
|
||||
| CombatStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedCombatSpecialAbility x -> x.apValue
|
||||
| CommandSpecialAbility x -> x.apValue
|
||||
| MagicStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedMagicalSpecialAbility x -> x.apValue
|
||||
| SpellSwordEnchantment x -> x.apValue
|
||||
| DaggerRitual x -> x.apValue
|
||||
| InstrumentEnchantment x -> x.apValue
|
||||
| AttireEnchantment x -> x.apValue
|
||||
| OrbEnchantment x -> x.apValue
|
||||
| WandEnchantment x -> x.apValue
|
||||
| BrawlingSpecialAbility x -> x.apValue
|
||||
| AncestorGlyph x -> x.apValue
|
||||
| CeremonialItemSpecialAbility x -> x.apValue
|
||||
| Sermon x -> x.apValue
|
||||
| LiturgicalStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedKarmaSpecialAbility x -> x.apValue
|
||||
| Vision x -> x.apValue
|
||||
| MagicalTradition x -> x.apValue
|
||||
| BlessedTradition x -> x.apValue
|
||||
| Paktgeschenk x -> x.apValue
|
||||
| SikaryanRaubSonderfertigkeit x -> x.apValue
|
||||
| LykanthropischeGabe x -> x.apValue
|
||||
| Talentstilsonderfertigkeit x -> x.apValue
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.apValue
|
||||
| Kugelzauber x -> x.apValue
|
||||
| Kesselzauber x -> x.apValue
|
||||
| Kappenzauber x -> x.apValue
|
||||
| Spielzeugzauber x -> x.apValue
|
||||
| Schalenzauber x -> x.apValue
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.apValue
|
||||
| SexSonderfertigkeit x -> x.apValue
|
||||
| Waffenzauber x -> x.apValue
|
||||
| Sichelritual x -> x.apValue
|
||||
| Ringzauber x -> x.apValue
|
||||
| Chronikzauber x -> x.apValue
|
||||
|
||||
let apValue' x =
|
||||
x |> apValue |> function
|
||||
| Some (Activatable_Shared.ApValue.Flat x) -> Some (OneOrMany.One x)
|
||||
| Some (PerLevel xs) -> Some (Many xs)
|
||||
| Some Option | None -> None
|
||||
|
||||
let max = function
|
||||
| Activatable.Advantage x -> x.max
|
||||
| Disadvantage x -> x.max
|
||||
| GeneralSpecialAbility x -> x.max
|
||||
| FatePointSpecialAbility x -> x.max
|
||||
| CombatSpecialAbility x -> x.max
|
||||
| MagicalSpecialAbility x -> x.max
|
||||
| StaffEnchantment x -> x.max
|
||||
| FamiliarSpecialAbility x -> x.max
|
||||
| KarmaSpecialAbility x -> x.max
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.max
|
||||
| CombatStyleSpecialAbility x -> x.max
|
||||
| AdvancedCombatSpecialAbility x -> x.max
|
||||
| CommandSpecialAbility x -> x.max
|
||||
| MagicStyleSpecialAbility x -> x.max
|
||||
| AdvancedMagicalSpecialAbility x -> x.max
|
||||
| SpellSwordEnchantment x -> x.max
|
||||
| DaggerRitual x -> x.max
|
||||
| InstrumentEnchantment x -> x.max
|
||||
| AttireEnchantment x -> x.max
|
||||
| OrbEnchantment x -> x.max
|
||||
| WandEnchantment x -> x.max
|
||||
| BrawlingSpecialAbility x -> x.max
|
||||
| AncestorGlyph x -> x.max
|
||||
| CeremonialItemSpecialAbility x -> x.max
|
||||
| Sermon x -> x.max
|
||||
| LiturgicalStyleSpecialAbility x -> x.max
|
||||
| AdvancedKarmaSpecialAbility x -> x.max
|
||||
| Vision x -> x.max
|
||||
| MagicalTradition x -> x.max
|
||||
| BlessedTradition x -> x.max
|
||||
| Paktgeschenk x -> x.max
|
||||
| SikaryanRaubSonderfertigkeit x -> x.max
|
||||
| LykanthropischeGabe x -> x.max
|
||||
| Talentstilsonderfertigkeit x -> x.max
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.max
|
||||
| Kugelzauber x -> x.max
|
||||
| Kesselzauber x -> x.max
|
||||
| Kappenzauber x -> x.max
|
||||
| Spielzeugzauber x -> x.max
|
||||
| Schalenzauber x -> x.max
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.max
|
||||
| SexSonderfertigkeit x -> x.max
|
||||
| Waffenzauber x -> x.max
|
||||
| Sichelritual x -> x.max
|
||||
| Ringzauber x -> x.max
|
||||
| Chronikzauber x -> x.max
|
||||
@@ -0,0 +1,37 @@
|
||||
(**
|
||||
* Convenient accessor functions for static Activatable entries.
|
||||
*
|
||||
* This module provides some accessor functions for accessing properties of a
|
||||
* static Activatable entry. The functions are named so that the name equals the
|
||||
* property name.
|
||||
*
|
||||
* It also provides a few functions for simple derived values.
|
||||
*)
|
||||
|
||||
val isActive : 'a Activatable_Dynamic.t -> bool
|
||||
(**
|
||||
* Is an Activatable entry active?
|
||||
*)
|
||||
|
||||
val isActiveM : 'a Activatable_Dynamic.t option -> bool
|
||||
(**
|
||||
* Is an Activatable, where the entry may not have been created, active?
|
||||
*)
|
||||
|
||||
val id : Activatable.t -> Id.Activatable.t
|
||||
|
||||
val id' : Activatable.t -> int
|
||||
|
||||
val idDeepVariant : Activatable.t -> Id.Activatable.Nested.t
|
||||
|
||||
val name : Activatable.t -> string
|
||||
|
||||
val selectOptions : Activatable.t -> SelectOption.map
|
||||
|
||||
val input : Activatable.t -> string option
|
||||
|
||||
val apValue : Activatable.t -> Activatable_Shared.ApValue.t option
|
||||
|
||||
val apValue' : Activatable.t -> int OneOrMany.t option
|
||||
|
||||
val max : Activatable.t -> int option
|
||||
@@ -0,0 +1,60 @@
|
||||
type t = {
|
||||
naming: Activatable_Active_Name.combinedName,
|
||||
active: Activatable_Convert.singleWithId,
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
validation: Activatable_Active_Validation.t,
|
||||
staticEntry: Activatable.t,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
};
|
||||
|
||||
let getActive =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
~addLevelToName,
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
) => {
|
||||
let naming =
|
||||
Activatable_Active_Name.getName(
|
||||
~addLevelToName,
|
||||
staticData,
|
||||
staticEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
let validation =
|
||||
Activatable_Active_Validation.isRemovalOrModificationValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
let {Activatable_Active_AdventurePointValue.apValue, isAutomatic} =
|
||||
Activatable_Active_AdventurePointValue.getApValueDifferenceOnChange(
|
||||
~isEntryToAdd,
|
||||
~automaticAdvantages=cache.automaticAdvantages,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
{
|
||||
naming,
|
||||
active: singleEntry,
|
||||
apValue,
|
||||
isAutomatic,
|
||||
validation,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
type t = {
|
||||
naming: Activatable_Active_Name.combinedName,
|
||||
active: Activatable_Convert.singleWithId,
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
validation: Activatable_Active_Validation.t,
|
||||
staticEntry: Activatable.t,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getActive cache staticData hero staticEntry maybeHeroEntry` adds display
|
||||
* options to an active entry, such as calculated AP, combined/generated name or
|
||||
* if it's valid to remove it.
|
||||
*/
|
||||
let getActive:
|
||||
(
|
||||
~isEntryToAdd: bool,
|
||||
~addLevelToName: bool,
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
Activatable_Convert.singleWithId
|
||||
) =>
|
||||
t;
|
||||
@@ -0,0 +1,590 @@
|
||||
module L = Ley_List;
|
||||
|
||||
open Ley_Option.Infix;
|
||||
open Ley_Function;
|
||||
open Activatable_Convert;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
type combinedApValue = {
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
let ensureFlat =
|
||||
fun
|
||||
| Advantage.Static.Flat(x) => Some(x)
|
||||
| PerLevel(_) => None;
|
||||
|
||||
let ensurePerLevel =
|
||||
fun
|
||||
| Advantage.Static.Flat(_) => None
|
||||
| PerLevel(x) => Some(x);
|
||||
|
||||
let getDefaultApValue = (staticEntry, singleHeroEntry) => {
|
||||
open Ley_List;
|
||||
|
||||
let sid1 = singleHeroEntry |> getOption1;
|
||||
let level = Ley_Option.fromOption(1, singleHeroEntry.level);
|
||||
let apValue =
|
||||
staticEntry
|
||||
|> Activatable_Accessors.apValue
|
||||
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
|
||||
|
||||
let optionApValue =
|
||||
sid1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOptionCost(staticEntry);
|
||||
|
||||
switch (optionApValue) {
|
||||
| Some(x) => Some(x)
|
||||
| None =>
|
||||
switch (apValue) {
|
||||
| Flat(x) => Some(x * level)
|
||||
| PerLevel(xs) =>
|
||||
switch (staticEntry) {
|
||||
| Advantage(_)
|
||||
| Disadvantage(_) => Ley_List.Safe.atMay(xs, level - 1)
|
||||
| SpecialAbility(_) =>
|
||||
xs |> take(Ley_Int.max(1, level)) |> Ley_List.sum |> (x => Some(x))
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let getPrinciplesObligationsMaxLevels = ({active, _}: Activatable_Dynamic.t) =>
|
||||
active
|
||||
|> Ley_List.foldr(
|
||||
(active: Activatable_Dynamic.single, (prevMax, prevSndMax)) =>
|
||||
switch (active.level, active.customCost) {
|
||||
// Only get the maximum from the current and the previous level, if
|
||||
// the current has no custom cost
|
||||
| (Some(activeLevel), None) =>
|
||||
if (activeLevel > prevMax) {
|
||||
(activeLevel, prevMax);
|
||||
} else {
|
||||
(prevMax, prevSndMax);
|
||||
}
|
||||
// Otherwise always return the previous max
|
||||
| _ => (prevMax, prevSndMax)
|
||||
},
|
||||
(0, 0),
|
||||
);
|
||||
|
||||
module IcAsIndex = {
|
||||
// Helper function to lookup entry, it's IC, converting it to an index and get
|
||||
// the correct value from the list of AP values
|
||||
let%private getApValueByIcAsIndexAux = (mp, getIc, id, apValues) =>
|
||||
Ley_IntMap.lookup(id, mp)
|
||||
>>= (
|
||||
static =>
|
||||
static |> getIc |> IC.icToIndex |> Ley_List.Safe.atMay(apValues)
|
||||
);
|
||||
|
||||
// Shortcut function for skills
|
||||
let%private getApValueFromSkillsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.skills, skill => skill.ic);
|
||||
|
||||
// Shortcut function for combat techniques
|
||||
let%private getApValueFromCombatTechniquesMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.combatTechniques, skill => skill.ic);
|
||||
|
||||
// Shortcut function for spells
|
||||
let%private getApValueFromSpellsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.spells, skill => skill.ic);
|
||||
|
||||
// Shortcut function for liturgical chants
|
||||
let%private getApValueFromLiturgicalChantsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.liturgicalChants, skill => skill.ic);
|
||||
|
||||
/**
|
||||
* `getApValueByIcAsIndex staticData apValue sid` takes the static data, the
|
||||
* AP value of the current entry and it's first option. It uses the IC of the
|
||||
* entry defined by the option as an index to retrieve the AP value from the
|
||||
* list of AP values defined for the entry. If the selected entry is not
|
||||
* present or if the entry does not define a list of AP values, `None` is
|
||||
* returned.
|
||||
*/
|
||||
let getApValueByIcAsIndex =
|
||||
(
|
||||
staticData: Static.t,
|
||||
apValue: Advantage.Static.apValue,
|
||||
sid: Id.Activatable.Option.t,
|
||||
) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (sid, apValue) {
|
||||
| (Preset(Skill(id)), PerLevel(apValues)) =>
|
||||
getApValueFromSkillsMapByIc(staticData, id, apValues)
|
||||
| (Preset(CombatTechnique(id)), PerLevel(apValues)) =>
|
||||
getApValueFromCombatTechniquesMapByIc(staticData, id, apValues)
|
||||
| (Preset(Spell(id)), PerLevel(apValues)) =>
|
||||
getApValueFromSpellsMapByIc(staticData, id, apValues)
|
||||
| (Preset(LiturgicalChant(id)), PerLevel(apValues)) =>
|
||||
getApValueFromLiturgicalChantsMapByIc(staticData, id, apValues)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value(s) how the spent AP value would change after removing the
|
||||
* respective entry.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
let getApValueDifferenceOnChangeByEntry =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleHeroEntry,
|
||||
) => {
|
||||
open Ley_List;
|
||||
|
||||
let sid1 = singleHeroEntry |> getOption1;
|
||||
let level = singleHeroEntry.level;
|
||||
let apValue =
|
||||
staticEntry
|
||||
|> Activatable_Accessors.apValue
|
||||
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
|
||||
| Advantage(Aptitude)
|
||||
| Advantage(ExceptionalSkill)
|
||||
| Advantage(ExceptionalCombatTechnique)
|
||||
| Advantage(WeaponAptitude)
|
||||
| Disadvantage(Incompetent)
|
||||
| SpecialAbility(AdaptionZauber)
|
||||
| SpecialAbility(FavoriteSpellwork)
|
||||
| SpecialAbility(Lieblingsliturgie)
|
||||
| SpecialAbility(Forschungsgebiet)
|
||||
| SpecialAbility(Expertenwissen)
|
||||
| SpecialAbility(Wissensdurst)
|
||||
| SpecialAbility(WegDerGelehrten)
|
||||
| SpecialAbility(WegDerKuenstlerin)
|
||||
| SpecialAbility(Fachwissen) =>
|
||||
singleHeroEntry
|
||||
|> getOption1
|
||||
>>= IcAsIndex.getApValueByIcAsIndex(staticData, apValue)
|
||||
| Disadvantage(PersonalityFlaw) =>
|
||||
switch (sid1) {
|
||||
| Some(Preset(Generic(selected_option))) =>
|
||||
let matchOption = (target_option, current) =>
|
||||
switch ((current: option(Id.Activatable.Option.t))) {
|
||||
| Some(Preset(Generic(x))) => x === target_option
|
||||
| _ => false
|
||||
};
|
||||
|
||||
let isPersonalityFlawNotPaid = (target_option, paid_entries_max) =>
|
||||
target_option === selected_option
|
||||
&& Ley_List.countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
|> matchOption(target_option)
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? paid_entries_max - 1 : paid_entries_max);
|
||||
|
||||
// 7 = "Prejudice" => more than one entry possible
|
||||
// more than one entry of Prejudice does not contribute to AP spent
|
||||
//
|
||||
// 8 = "Unworldly" => more than one entry possible
|
||||
// more than two entries of Unworldly do not contribute to AP spent
|
||||
//
|
||||
// In both cases, removing the entry would not change AP so it has to
|
||||
// return 0.
|
||||
if (isPersonalityFlawNotPaid(7, 1) || isPersonalityFlawNotPaid(8, 2)) {
|
||||
Some(0);
|
||||
} else {
|
||||
getSelectOptionCost(staticEntry, Generic(selected_option));
|
||||
};
|
||||
| _ => None
|
||||
}
|
||||
| Disadvantage(Principles)
|
||||
| Disadvantage(Obligations) =>
|
||||
level
|
||||
>>= (
|
||||
level => {
|
||||
// This is the highest and the second-highest level of this entry at the
|
||||
// moment.
|
||||
let (maxLevel, sndMaxLevel) =
|
||||
getPrinciplesObligationsMaxLevels(heroEntry);
|
||||
|
||||
// If there is more than one entry on the same level if this entry is
|
||||
// active, it won't affect AP spent at all. Thus, if the entry is to
|
||||
// be added, there must be at least one (> 0) entry for this rule to
|
||||
// take effect.
|
||||
//
|
||||
// If the entry is not the one with the highest level, adding or
|
||||
// removing it won't affect AP spent at all
|
||||
if (maxLevel > level
|
||||
|| countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.elem(level, e.level),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? 0 : 1)) {
|
||||
None;
|
||||
} else {
|
||||
// Otherwise, the level difference results in the cost.
|
||||
apValue |> ensureFlat <&> ( * )(level - sndMaxLevel);
|
||||
};
|
||||
}
|
||||
)
|
||||
| Disadvantage(BadHabit) =>
|
||||
apValue
|
||||
|> ensureFlat
|
||||
|> Ley_Option.find(_ =>
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? 2 : 3)
|
||||
)
|
||||
| SpecialAbility(SkillSpecialization) =>
|
||||
sid1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (
|
||||
skill =>
|
||||
// Multiply number of final occurences of the
|
||||
// same skill...
|
||||
(
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
|> Ley_Option.elem(
|
||||
Id.Activatable.Option.Preset(Skill(skill.id)),
|
||||
)
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
+ (isEntryToAdd ? 1 : 0)
|
||||
)
|
||||
// ...with the skill's IC
|
||||
* IC.getApForActivatation(skill.ic)
|
||||
)
|
||||
| SpecialAbility(Language) =>
|
||||
level
|
||||
>>= (
|
||||
fun
|
||||
// Native Tongue (level 4) does not cost anything
|
||||
| 4 => Some(0)
|
||||
| level => apValue |> ensureFlat <&> ( * )(level)
|
||||
)
|
||||
| SpecialAbility(PropertyKnowledge)
|
||||
| SpecialAbility(AspectKnowledge) =>
|
||||
apValue
|
||||
|> ensurePerLevel
|
||||
>>= (
|
||||
apPerLevel => {
|
||||
// Ignore custom cost activations in terms of calculated cost
|
||||
let amountActive =
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
let index = amountActive + (isEntryToAdd ? 0 : (-1));
|
||||
|
||||
Ley_List.Safe.atMay(apPerLevel, index);
|
||||
}
|
||||
)
|
||||
| SpecialAbility(TraditionWitches) =>
|
||||
// There are two disadvantages that, when active, decrease the cost of
|
||||
// this tradition by 10 AP each
|
||||
let decreaseCost = (id, cost) =>
|
||||
hero.disadvantages
|
||||
|> Ley_IntMap.lookup(id)
|
||||
|> Activatable_Accessors.isActiveM
|
||||
? cost - 10 : cost;
|
||||
|
||||
apValue
|
||||
|> ensureFlat
|
||||
<&> (
|
||||
flatAp =>
|
||||
flatAp
|
||||
|> decreaseCost(Id.Disadvantage.toInt(NoFlyingBalm))
|
||||
|> decreaseCost(Id.Disadvantage.toInt(NoFamiliar))
|
||||
);
|
||||
| SpecialAbility(Recherchegespuer) =>
|
||||
// The AP cost for this SA consist of two parts: AP based on the IC of
|
||||
// the main subject (from "SA_531"/Wissensdurst) in addition to AP based
|
||||
// on the IC of the side subject selected in this SA.
|
||||
|
||||
hero.specialAbilities
|
||||
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Wissensdurst))
|
||||
>>= (
|
||||
wissensdurst =>
|
||||
apValue
|
||||
|> ensurePerLevel
|
||||
>>= (
|
||||
apPerLevel => {
|
||||
let getCostFromHeroEntry = entry =>
|
||||
entry
|
||||
|> getOption1
|
||||
>>= IcAsIndex.getApValueByIcAsIndex(
|
||||
staticData,
|
||||
PerLevel(apPerLevel),
|
||||
);
|
||||
|
||||
Ley_Option.liftM2(
|
||||
(+),
|
||||
// Cost for side subject
|
||||
getCostFromHeroEntry(singleHeroEntry),
|
||||
// Cost for main subject from Wissensdurst
|
||||
wissensdurst.active
|
||||
|> Ley_Option.listToOption
|
||||
>>= (
|
||||
fst =>
|
||||
fst
|
||||
|> singleToSingleWithId(heroEntry, 0)
|
||||
|> getCostFromHeroEntry
|
||||
),
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
| SpecialAbility(LanguageSpecializations) =>
|
||||
apValue
|
||||
|> ensureFlat
|
||||
>>= (
|
||||
flatAp =>
|
||||
sid1
|
||||
>>= getGenericId
|
||||
>>= (
|
||||
languageId =>
|
||||
hero.specialAbilities
|
||||
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Language))
|
||||
>>= (
|
||||
language =>
|
||||
language.active
|
||||
|> Ley_List.find((e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
>>= getGenericId
|
||||
|> Ley_Option.elem(languageId)
|
||||
)
|
||||
>>= (
|
||||
selectedLanguage =>
|
||||
selectedLanguage.level
|
||||
<&> (
|
||||
fun
|
||||
| 4 => 0
|
||||
| _ => flatAp
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
| SpecialAbility(Handwerkskunst)
|
||||
| SpecialAbility(KindDerNatur)
|
||||
| SpecialAbility(KoerperlichesGeschick)
|
||||
| SpecialAbility(SozialeKompetenz)
|
||||
| SpecialAbility(Universalgenie) =>
|
||||
singleHeroEntry.options
|
||||
|> Ley_List.take(3)
|
||||
|> Ley_Option.mapOption(
|
||||
IcAsIndex.getApValueByIcAsIndex(staticData, apValue),
|
||||
)
|
||||
|> Ley_List.sum
|
||||
|> Ley_Option.ensure((<)(0))
|
||||
| _ => getDefaultApValue(staticEntry, singleHeroEntry)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the AP you get when addiing or removing the entry.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
let getApValueDifferenceOnChange =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
~automaticAdvantages,
|
||||
staticData,
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleHeroEntry,
|
||||
) => {
|
||||
let isAutomatic = Ley_List.elem(singleHeroEntry.id, automaticAdvantages);
|
||||
|
||||
let modifyAbs =
|
||||
switch (staticEntry) {
|
||||
| Activatable.Disadvantage(_) => Ley_Int.negate
|
||||
| Advantage(_)
|
||||
| SpecialAbility(_) => id
|
||||
};
|
||||
|
||||
switch (singleHeroEntry.customCost) {
|
||||
| Some(customCost) => {apValue: modifyAbs(customCost), isAutomatic}
|
||||
| None =>
|
||||
getApValueDifferenceOnChangeByEntry(
|
||||
~isEntryToAdd,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleHeroEntry,
|
||||
)
|
||||
|> Ley_Option.fromOption(0)
|
||||
|> modifyAbs
|
||||
|> (apValue => {apValue, isAutomatic})
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the difference to the value from `getApValueDifferenceOnChange` to be
|
||||
* able to calculate the correct AP spent.
|
||||
*
|
||||
* A positive return value means that the entry uses *more* AP than what
|
||||
* `getApValueDifferenceOnChange` returned for all activations, so that both
|
||||
* return values can be added to form the final AP spent.
|
||||
*
|
||||
* Note, that disadvantages still return positive values if they actually cost
|
||||
* AP.
|
||||
*/
|
||||
let getApSpentDifference = (staticEntry, heroEntry: Activatable_Dynamic.t) => {
|
||||
let apValue = staticEntry |> Activatable_Accessors.apValue;
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
|
||||
| Disadvantage(Principles)
|
||||
| Disadvantage(Obligations) =>
|
||||
let (maxLevel, sndMaxLevel) =
|
||||
getPrinciplesObligationsMaxLevels(heroEntry);
|
||||
|
||||
let singlesAtMaxLevel =
|
||||
L.countBy(
|
||||
fun
|
||||
| ({level: Some(level), _}: Activatable_Dynamic.single) =>
|
||||
level === maxLevel
|
||||
| _ => false,
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
let baseApValue =
|
||||
switch (apValue) {
|
||||
| Some(Flat(value)) => value
|
||||
| Some(PerLevel(_))
|
||||
| None => 0
|
||||
};
|
||||
|
||||
singlesAtMaxLevel > 1
|
||||
// In this case, all max level entries would be 0, so this needs to be
|
||||
// the full value for the max level
|
||||
? baseApValue * maxLevel
|
||||
// otherwise, the max level entry costs the difference between max and
|
||||
// second max, so we need to fill to actual value so that
|
||||
// currentDifference + baseApValue * sndMaxLevel = total AP value
|
||||
: baseApValue * sndMaxLevel;
|
||||
| Disadvantage(PersonalityFlaw) =>
|
||||
let counterBySelectOption =
|
||||
heroEntry.active
|
||||
|> SelectOption.Map.countByM(
|
||||
fun
|
||||
| (
|
||||
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
|
||||
) =>
|
||||
Some(id)
|
||||
| _ => None,
|
||||
);
|
||||
|
||||
let getDiffBySelectOption = (selectOptionId, paidEntries, counter) => {
|
||||
counter
|
||||
|> SelectOption.Map.lookup(Generic(selectOptionId))
|
||||
|> Ley_Option.fromOption(0) > paidEntries
|
||||
? Activatable_SelectOptions.getSelectOptionCost(
|
||||
staticEntry,
|
||||
Generic(selectOptionId),
|
||||
)
|
||||
|> Ley_Option.option(0, apValue =>
|
||||
apValue * paidEntries |> Ley_Int.negate
|
||||
)
|
||||
: 0;
|
||||
};
|
||||
|
||||
getDiffBySelectOption(7, 1, counterBySelectOption)
|
||||
+ getDiffBySelectOption(8, 2, counterBySelectOption);
|
||||
| Disadvantage(BadHabit) =>
|
||||
heroEntry.active
|
||||
// Ignore entries with custom cost
|
||||
|> Ley_List.countBy(
|
||||
fun
|
||||
| ({customCost: None, _}: Activatable_Dynamic.single) => true
|
||||
| _ => false,
|
||||
)
|
||||
> 3
|
||||
// if more than three entries are active, a 0 is displayed, so we need to
|
||||
// keep the AP value for three entries.
|
||||
? switch (apValue) {
|
||||
| Some(Flat(flatApValue)) => flatApValue * (-3)
|
||||
| _ => 0
|
||||
}
|
||||
: 0
|
||||
| SpecialAbility(SkillSpecialization) =>
|
||||
let counterBySelectOption =
|
||||
heroEntry.active
|
||||
|> SelectOption.Map.countByM(
|
||||
fun
|
||||
| (
|
||||
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
|
||||
) =>
|
||||
Some(id)
|
||||
| _ => None,
|
||||
);
|
||||
|
||||
counterBySelectOption
|
||||
|> SelectOption.Map.foldrWithKey(
|
||||
(id, count, accDiff) => {
|
||||
switch (
|
||||
Activatable_SelectOptions.getSelectOption(staticEntry, id)
|
||||
) {
|
||||
| Some({staticEntry: Some(Skill(skill)), _}) =>
|
||||
// The sum of displayed AP values without taking IC into account.
|
||||
let currentIcFactorForSkill = count * count;
|
||||
// The sum of actual AP values without taking IC into account.
|
||||
let actualIcFactorForSkill = Math.gsum(1, count);
|
||||
|
||||
// Apply IC multiplier to get final AP value
|
||||
IC.getApForActivatation(skill.ic)
|
||||
* (actualIcFactorForSkill - currentIcFactorForSkill)
|
||||
+ accDiff;
|
||||
| _ => accDiff
|
||||
}
|
||||
},
|
||||
0,
|
||||
);
|
||||
| SpecialAbility(PropertyKnowledge)
|
||||
| SpecialAbility(AspectKnowledge) =>
|
||||
let entryCount = heroEntry.active |> Ley_List.length;
|
||||
|
||||
switch (apValue) {
|
||||
| Some(PerLevel(apValues)) =>
|
||||
let currentSum =
|
||||
Ley_List.Safe.atMay(apValues, entryCount)
|
||||
|> Ley_Option.option(0, ( * )(entryCount));
|
||||
let actualSum = apValues |> Ley_List.take(entryCount) |> Ley_List.sum;
|
||||
|
||||
actualSum - currentSum;
|
||||
| _ => 0
|
||||
};
|
||||
| _ => 0
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Functions for working on adventure point values for Activatable entries.
|
||||
*
|
||||
* This module provides types and functions for calculating and keeping
|
||||
* adventure point value calculations in genereal and specific to certain
|
||||
* entries.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* The generated AP value for an Activatable. Contains the generated AP value
|
||||
* as well as if the entry is automatically added by the race, in which case
|
||||
* no additional AP from the hero are needed.
|
||||
*/
|
||||
type combinedApValue = {
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getApValue isEntryToAdd automaticAdvantages staticData hero
|
||||
* staticActivatable heroActivatable singleEntry` returns the AP you get when
|
||||
* removing the passed `singleEntry`. It also returns if the entry has been
|
||||
* automatically granted by the race.
|
||||
*
|
||||
* `isEntryToAdd` has to be `true` if `singleEntry` has not been added
|
||||
* to the list of active entries yet, otherwise `false`. `automaticAdvantages`
|
||||
* is the list of automatic advantage IDs.
|
||||
*/
|
||||
let getApValueDifferenceOnChange:
|
||||
(
|
||||
~isEntryToAdd: bool,
|
||||
~automaticAdvantages: list(int),
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
singleWithId
|
||||
) =>
|
||||
combinedApValue;
|
||||
@@ -0,0 +1,507 @@
|
||||
open Ley_Option;
|
||||
open Ley_Option.Infix;
|
||||
open Ley_Function;
|
||||
open Activatable_Convert;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
let getDefaultNameAddition = (staticEntry, heroEntry) => {
|
||||
let input = Activatable_Accessors.input(staticEntry);
|
||||
let selectOptions = Activatable_Accessors.selectOptions(staticEntry);
|
||||
|
||||
let sid = heroEntry |> getOption1;
|
||||
let sid2 = heroEntry |> getOption2;
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (input, sid, sid2) {
|
||||
// Text input
|
||||
| (Some(_), Some(CustomInput(str)), None) => Some(str)
|
||||
// Select option and text input
|
||||
| (Some(_), Some(Preset(id)), Some(CustomInput(str)))
|
||||
when SelectOption.Map.size(selectOptions) > 0 =>
|
||||
Some(
|
||||
(id |> getSelectOptionName(staticEntry) |> fromOption(""))
|
||||
++ ": "
|
||||
++ str,
|
||||
)
|
||||
// Plain select option
|
||||
| (None, Some(Preset(id)), None) => getSelectOptionName(staticEntry, id)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A lot of entries have customization options: Text input, select option or
|
||||
* both. This function creates a string that can be appended to the `name`
|
||||
* property of the respective record to create the full active name.
|
||||
*/
|
||||
let getEntrySpecificNameAddition =
|
||||
(staticData: Static.t, staticEntry, heroEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(entry.id)) {
|
||||
| Aptitude
|
||||
| ExceptionalSkill =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(Skill(id)) =>
|
||||
lookupMap(id, staticData.skills, x => x.name)
|
||||
| Preset(Spell(id)) =>
|
||||
lookupMap(id, staticData.spells, x => x.name)
|
||||
| Preset(LiturgicalChant(id)) =>
|
||||
lookupMap(id, staticData.liturgicalChants, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| ExceptionalCombatTechnique
|
||||
| WeaponAptitude =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(CombatTechnique(id)) =>
|
||||
lookupMap(id, staticData.combatTechniques, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| HatredFor =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
|> liftM2(
|
||||
(type_, frequency: SelectOption.t) =>
|
||||
type_ ++ " (" ++ frequency.name ++ ")",
|
||||
getOption2(heroEntry) >>= getCustomInput,
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
| Disadvantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(entry.id)) {
|
||||
| Incompetent =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (x => x.name)
|
||||
| PersonalityFlaw =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
<&> (
|
||||
option1 =>
|
||||
(
|
||||
switch (option1.id) {
|
||||
// Get the input if Prejudice or Unworldly is selected
|
||||
| Generic(7 | 8) => heroEntry |> getOption2 >>= getCustomInput
|
||||
// Otherwise ignore any additional options
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|> option(option1.name, specialInput =>
|
||||
option1.name ++ ": " ++ specialInput
|
||||
)
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
| SpecialAbility(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(entry.id)) {
|
||||
| AdaptionZauber
|
||||
| FavoriteSpellwork =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(Spell(id)) =>
|
||||
lookupMap(id, staticData.spells, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| TraditionSavant
|
||||
| Forschungsgebiet
|
||||
| Expertenwissen
|
||||
| Wissensdurst
|
||||
| Recherchegespuer =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (x => x.name)
|
||||
| Lieblingsliturgie =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(LiturgicalChant(id)) =>
|
||||
lookupMap(id, staticData.liturgicalChants, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| SkillSpecialization =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
>>= (
|
||||
skill =>
|
||||
heroEntry
|
||||
|> getOption2
|
||||
>>= (
|
||||
option2 =>
|
||||
(
|
||||
switch (option2) {
|
||||
// If input string use input
|
||||
| CustomInput(x) => Some(x)
|
||||
// Otherwise lookup application name
|
||||
| Preset(Generic(id)) =>
|
||||
skill.applications
|
||||
|> Ley_IntMap.find((a: Skill.Static.Application.t) =>
|
||||
a.id === id
|
||||
)
|
||||
<&> (a => a.name)
|
||||
| Preset(_) => None
|
||||
}
|
||||
)
|
||||
// Merge skill name and application name
|
||||
<&> (appName => skill.name ++ ": " ++ appName)
|
||||
)
|
||||
)
|
||||
| Exorzist =>
|
||||
switch (heroEntry.level) {
|
||||
| Some(1) =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOptionName(staticEntry)
|
||||
| _ => None
|
||||
}
|
||||
| SpellEnhancement as entryId
|
||||
| ChantEnhancement as entryId =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
>>= (
|
||||
enhancement =>
|
||||
enhancement.enhancementTarget
|
||||
>>= (
|
||||
id =>
|
||||
(
|
||||
switch (entryId) {
|
||||
| SpellEnhancement =>
|
||||
Ley_IntMap.lookup(id, staticData.spells) <&> (x => x.name)
|
||||
| _ =>
|
||||
Ley_IntMap.lookup(id, staticData.liturgicalChants)
|
||||
<&> (x => x.name)
|
||||
}
|
||||
)
|
||||
<&> (targetName => targetName ++ ": " ++ enhancement.name)
|
||||
)
|
||||
)
|
||||
| TraditionArcaneBard =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getGenericId
|
||||
>>= flip(Ley_IntMap.lookup, staticData.arcaneBardTraditions)
|
||||
<&> (trad => trad.name)
|
||||
| TraditionArcaneDancer =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getGenericId
|
||||
>>= flip(Ley_IntMap.lookup, staticData.arcaneDancerTraditions)
|
||||
<&> (trad => trad.name)
|
||||
| LanguageSpecializations =>
|
||||
liftM2(
|
||||
getSelectOption,
|
||||
Ley_IntMap.lookup(
|
||||
Id.SpecialAbility.toInt(Language),
|
||||
staticData.specialAbilities,
|
||||
)
|
||||
<&> (specialAbility => Activatable.SpecialAbility(specialAbility)),
|
||||
getOption1(heroEntry)
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId,
|
||||
)
|
||||
|> join
|
||||
>>= (
|
||||
language =>
|
||||
heroEntry
|
||||
|> getOption2
|
||||
>>= (
|
||||
option2 =>
|
||||
(
|
||||
switch (option2) {
|
||||
| CustomInput(str) => Some(str)
|
||||
| Preset(Generic(specializationId)) =>
|
||||
language.specializations
|
||||
>>= (
|
||||
specializations =>
|
||||
Ley_List.Safe.atMay(
|
||||
specializations,
|
||||
specializationId - 1,
|
||||
)
|
||||
)
|
||||
| Preset(_) => None
|
||||
}
|
||||
)
|
||||
<&> (specialization => language.name ++ ": " ++ specialization)
|
||||
)
|
||||
)
|
||||
| Fachwissen =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
>>= (
|
||||
skill => {
|
||||
let applications =
|
||||
skill.applications
|
||||
|> Ley_IntMap.filter((app: Skill.Static.Application.t) =>
|
||||
app.prerequisite |> isNone
|
||||
);
|
||||
|
||||
[heroEntry |> getOption2, heroEntry |> getOption3]
|
||||
|> mapOption(option =>
|
||||
option
|
||||
>>= getGenericId
|
||||
>>= (
|
||||
opt =>
|
||||
applications
|
||||
|> Ley_IntMap.find((app: Skill.Static.Application.t) =>
|
||||
app.id === opt
|
||||
)
|
||||
<&> (app => app.name)
|
||||
)
|
||||
)
|
||||
|> ensure(apps => apps |> Ley_List.length |> (===)(2))
|
||||
<&> (
|
||||
apps =>
|
||||
apps
|
||||
|> AdvancedFiltering.sortStrings(staticData)
|
||||
|> Intl.ListFormat.format(Conjunction, staticData)
|
||||
|> (appsStr => skill.name ++ ": " ++ appsStr)
|
||||
);
|
||||
}
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getDisAdvLevelStr = RomanInt.intToRoman;
|
||||
|
||||
let getSpecialAbilityLevelStr = level =>
|
||||
(level > 1 ? "I" ++ Chars.nobr ++ "–" ++ Chars.nobr : "")
|
||||
++ getDisAdvLevelStr(level);
|
||||
|
||||
/**
|
||||
* Gets the level string that hast to be appended (with a non-breaking space!)
|
||||
* to the name. This string is aware of differences between dis/advantages and
|
||||
* special abilties as well as it handles the Native Tongue level for
|
||||
* languages.
|
||||
*/
|
||||
let getLevelName = (staticData: Static.t, staticEntry, singleHeroEntry) =>
|
||||
switch (staticEntry, singleHeroEntry.level) {
|
||||
| (Activatable.Advantage(_), Some(level))
|
||||
| (Disadvantage(_), Some(level)) => Some(getDisAdvLevelStr(level))
|
||||
| (SpecialAbility(staticEntry), Some(level)) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(staticEntry.id)) {
|
||||
// Language level 4 = Native Tongue and thus needs a special name
|
||||
| Language when level === 4 =>
|
||||
Some(staticData.messages.specialabilities_nativetonguelevel)
|
||||
| _ => Some(getSpecialAbilityLevelStr(level))
|
||||
}
|
||||
)
|
||||
| (Advantage(_) | Disadvantage(_) | SpecialAbility(_), None) => None
|
||||
};
|
||||
|
||||
/**
|
||||
* Some entries cannot use the default `name` property from wiki entries. The
|
||||
* value returned by may not use the default `name` property. For all entries
|
||||
* that do not need to handle a specific display format, the default `name`
|
||||
* property is used.
|
||||
*/
|
||||
let getEntrySpecificNameReplacements =
|
||||
(~addLevelToName, staticEntry, nameAddition, levelName) => {
|
||||
let name = Activatable_Accessors.name(staticEntry);
|
||||
|
||||
let flatLevelName =
|
||||
addLevelToName ? option("", (++)(Chars.nbsp), levelName) : "";
|
||||
|
||||
let mapNameAddition = f => option(name, f, nameAddition);
|
||||
|
||||
let mapDefaultWithParens = () =>
|
||||
mapNameAddition(add => name ++ " (" ++ add ++ ")");
|
||||
|
||||
let mapDefaultWithoutParens = () =>
|
||||
mapNameAddition(add => name ++ " " ++ add);
|
||||
|
||||
let addSndInParens = snd =>
|
||||
Ley_List.Extra.replaceStr(")", ": " ++ snd ++ ")");
|
||||
|
||||
switch (staticEntry) {
|
||||
| Advantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(entry.id)) {
|
||||
| ImmunityToPoison
|
||||
| ImmunityToDisease
|
||||
| HatredFor => mapDefaultWithoutParens()
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
| Disadvantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(entry.id)) {
|
||||
| AfraidOf => mapDefaultWithoutParens() ++ flatLevelName
|
||||
| Principles
|
||||
| Obligations =>
|
||||
option(
|
||||
name ++ flatLevelName,
|
||||
nameAddition => name ++ flatLevelName ++ " (" ++ nameAddition ++ ")",
|
||||
nameAddition,
|
||||
)
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
| SpecialAbility(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(entry.id)) {
|
||||
| GebieterDesAspekts => mapDefaultWithoutParens()
|
||||
| TraditionArcaneBard
|
||||
| TraditionArcaneDancer
|
||||
| TraditionSavant => mapNameAddition(flip(addSndInParens, name))
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
type combinedName = {
|
||||
name: string,
|
||||
baseName: string,
|
||||
addName: option(string),
|
||||
levelName: option(string),
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns name, splitted and combined, of advantage/disadvantage/special
|
||||
* ability as a Option (in case the wiki entry does not exist).
|
||||
*/
|
||||
let getName = (~addLevelToName, staticData, staticEntry, heroEntry) => {
|
||||
let addName =
|
||||
getEntrySpecificNameAddition(staticData, staticEntry, heroEntry);
|
||||
|
||||
let levelName = getLevelName(staticData, staticEntry, heroEntry);
|
||||
|
||||
let fullName =
|
||||
getEntrySpecificNameReplacements(
|
||||
~addLevelToName,
|
||||
staticEntry,
|
||||
addName,
|
||||
levelName,
|
||||
);
|
||||
|
||||
{
|
||||
name: fullName,
|
||||
baseName: Activatable_Accessors.name(staticEntry),
|
||||
addName,
|
||||
levelName,
|
||||
};
|
||||
};
|
||||
/*
|
||||
/**
|
||||
* Returns the name of the given object. If the object is a string, it returns
|
||||
* the string.
|
||||
*/
|
||||
export const getFullName =
|
||||
(obj: string | ActiveActivatable): string => {
|
||||
if (typeof obj === "string") {
|
||||
return obj
|
||||
}
|
||||
|
||||
return obj.nameAndCost.naming.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the full special ability name and returns only the text between
|
||||
* parentheses. If no parentheses were found, returns an empty string.
|
||||
*/
|
||||
export const getBracketedNameFromFullName =
|
||||
(full_name: string): string => {
|
||||
const result = /\((?<subname>.+)\)/u .exec (full_name)
|
||||
|
||||
if (result === null || result .groups === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return result .groups .subname
|
||||
}
|
||||
|
||||
/**
|
||||
* `compressList :: L10n -> [ActiveActivatable] -> String`
|
||||
*
|
||||
* Takes a list of active Activatables and merges them together. Used to display
|
||||
* lists of Activatables on character sheet.
|
||||
*/
|
||||
export const compressList =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(xs: List<Record<ActiveActivatable>>): string => {
|
||||
const grouped_xs =
|
||||
elems (groupByKey<Record<ActiveActivatable>, string> (AAA_.id) (xs))
|
||||
|
||||
return pipe (
|
||||
map (
|
||||
ifElse<List<Record<ActiveActivatable>>>
|
||||
(xs_group => flength (xs_group) === 1)
|
||||
(pipe (listToOption, Option ("") (AAA_.name)))
|
||||
(xs_group => pipe (
|
||||
map ((x: Record<ActiveActivatable>) => {
|
||||
const levelPart =
|
||||
pipe (
|
||||
AAA_.level,
|
||||
fmap (pipe (toRoman, appendStr (" "))),
|
||||
fromOption ("")
|
||||
)
|
||||
(x)
|
||||
|
||||
const selectOptionPart =
|
||||
fromOption ("") (AAA_.addName (x))
|
||||
|
||||
return selectOptionPart + levelPart
|
||||
}),
|
||||
sortStrings (staticData),
|
||||
intercalate (", "),
|
||||
x => ` (${x})`,
|
||||
x => option ("")
|
||||
((r: Record<ActiveActivatable>) =>
|
||||
AAA_.baseName (r) + x)
|
||||
(listToOption (xs_group))
|
||||
)
|
||||
(xs_group))
|
||||
),
|
||||
sortStrings (staticData),
|
||||
intercalate (", ")
|
||||
)
|
||||
(grouped_xs)
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Generate full names for an active Activatanle entry.
|
||||
*
|
||||
* This module provides types and functions to generate a full name string for
|
||||
* an active Activatable entry.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* The generated name for an Activatable. Contains the generated name and it's
|
||||
* parts: The base entry name, the generated addition and the converted level
|
||||
* name.
|
||||
*/
|
||||
type combinedName = {
|
||||
name: string,
|
||||
baseName: string,
|
||||
addName: option(string),
|
||||
levelName: option(string),
|
||||
};
|
||||
|
||||
/**
|
||||
* `getName addLevelToName staticData staticActivatable singleEntry` returns
|
||||
* the name, splitted and combined, of the passed `singleEntry`.
|
||||
*/
|
||||
let getName:
|
||||
(~addLevelToName: bool, Static.t, Activatable.t, singleWithId) =>
|
||||
combinedName;
|
||||
@@ -0,0 +1,449 @@
|
||||
module I = Ley_Int;
|
||||
module IM = Ley_IntMap;
|
||||
module IS = Ley_IntSet;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
type t = {
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
disabled: bool,
|
||||
};
|
||||
|
||||
// const hasRequiredMinimumLevel =
|
||||
// (min_level: Maybe<number>) => (max_level: Maybe<number>): boolean =>
|
||||
// isJust (max_level) && isJust (min_level)
|
||||
|
||||
let getActivesById = (mp, id) =>
|
||||
IM.lookup(id, mp)
|
||||
|> O.option([], ({active, _}: Activatable_Dynamic.t) => active);
|
||||
|
||||
let isNotRequiredByDependencies =
|
||||
(
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleEntry: Activatable_Convert.singleWithId,
|
||||
) =>
|
||||
heroEntry.dependencies
|
||||
// Filter out every dependency that can be matched by another entry
|
||||
|> Dependencies.Flatten.flattenActivatableDependencies(
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => getActivesById(hero.advantages)
|
||||
| Disadvantage(_) => getActivesById(hero.disadvantages)
|
||||
| SpecialAbility(_) => getActivesById(hero.specialAbilities)
|
||||
},
|
||||
singleEntry.id,
|
||||
)
|
||||
|> L.all((dep: Activatable_Dynamic.dependency) =>
|
||||
!
|
||||
Dependencies.Activatable.isDependencyMatched(
|
||||
dep,
|
||||
Activatable_Convert.singleWithIdToSingle(singleEntry),
|
||||
)
|
||||
// If the entry matched the dependency, try if other activations of the
|
||||
// same entry may still satisfy the dependency, so that it can still be
|
||||
// removed
|
||||
|| L.Index.deleteAt(singleEntry.index, heroEntry.active)
|
||||
|> L.any(Dependencies.Activatable.isDependencyMatched(dep))
|
||||
);
|
||||
|
||||
let isEntrySpecificRemovalValid =
|
||||
(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleEntry: Activatable_Convert.singleWithId,
|
||||
) =>
|
||||
if (Tradition.Magical.isTraditionId(staticData, singleEntry.id)) {
|
||||
let activeTraditions = cache.magicalTraditions;
|
||||
|
||||
let hasMultipleTraditions = L.length(activeTraditions) > 1;
|
||||
|
||||
hasMultipleTraditions
|
||||
|| !ActivatableSkills.hasActiveSkillEntries(Spells, hero)
|
||||
&& IS.null(hero.cantrips);
|
||||
} else if (Tradition.Blessed.isTraditionId(staticData, singleEntry.id)) {
|
||||
!ActivatableSkills.hasActiveSkillEntries(LiturgicalChants, hero)
|
||||
&& IS.null(hero.blessings);
|
||||
} else {
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Advantage.(
|
||||
switch (fromInt(staticAdvantage.id)) {
|
||||
| ExceptionalSkill =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Skill(id)), ..._] =>
|
||||
// value of target skill
|
||||
let value =
|
||||
IM.lookup(id, hero.skills) |> Skill.Dynamic.getValueDef;
|
||||
|
||||
// amount of active Exceptional Skill advantages for the same skill
|
||||
let countSameSkill =
|
||||
L.countBy(
|
||||
(active: Activatable_Dynamic.single) =>
|
||||
switch (active.options) {
|
||||
| [Preset(Skill(otherId)), ..._] => otherId === id
|
||||
| _ => false
|
||||
},
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
// if the maximum skill rating is reached removal needs to be
|
||||
// disabled
|
||||
value >= cache.startExperienceLevel.maxSkillRating + countSameSkill;
|
||||
| _ => true
|
||||
}
|
||||
| ExceptionalCombatTechnique =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(CombatTechnique(id)), ..._] =>
|
||||
// value of target combat technique
|
||||
let value =
|
||||
IM.lookup(id, hero.combatTechniques)
|
||||
|> CombatTechnique.Dynamic.getValueDef;
|
||||
|
||||
// if the maximum combat technique rating is reached removal needs
|
||||
// to be disabled
|
||||
value >= cache.startExperienceLevel.maxCombatTechniqueRating + 1;
|
||||
| _ => true
|
||||
}
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| Literacy =>
|
||||
switch (
|
||||
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
|
||||
cache.matchingLanguagesScripts.scriptsWithMatchingLanguages,
|
||||
singleEntry.options,
|
||||
) {
|
||||
// requiring entry must be active, the list of scripts must contain
|
||||
// only one element and that element has to match the current script
|
||||
// to prohibit removing the entry
|
||||
| (
|
||||
true,
|
||||
[onlyScriptWithLanguage],
|
||||
[Preset(Generic(scriptId)), ..._],
|
||||
) =>
|
||||
onlyScriptWithLanguage !== scriptId
|
||||
| _ => true
|
||||
}
|
||||
| Language =>
|
||||
switch (
|
||||
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
|
||||
cache.matchingLanguagesScripts.languagesWithMatchingScripts,
|
||||
singleEntry.options,
|
||||
) {
|
||||
// requiring entry must be active, the list of languages must contain
|
||||
// only one element and that element has to match the current language
|
||||
// to prohibit removing the entry
|
||||
| (
|
||||
true,
|
||||
[onlyLanguageWithScript],
|
||||
[Preset(Generic(languageId)), ..._],
|
||||
) =>
|
||||
onlyLanguageWithScript !== languageId
|
||||
| _ => true
|
||||
}
|
||||
| PropertyKnowledge =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Generic(propertyId)), ..._] =>
|
||||
hero.spells
|
||||
// If there is any spell with matching property above SR 14...
|
||||
|> IM.any((heroSpell: ActivatableSkill.Dynamic.t) =>
|
||||
ActivatableSkill.Dynamic.valueToInt(heroSpell.value) > 14
|
||||
&& IM.lookup(heroSpell.id, staticData.spells)
|
||||
|> O.option(true, ({Spell.Static.property, _}) =>
|
||||
property === propertyId
|
||||
)
|
||||
)
|
||||
// ...prohibit removal
|
||||
|> (!)
|
||||
| _ => true
|
||||
}
|
||||
| AspectKnowledge =>
|
||||
let activeAspects =
|
||||
Activatable_SelectOptions.mapActiveOptions1(
|
||||
fun
|
||||
| Preset(Generic(aspectId)) => Some(aspectId)
|
||||
| _ => None,
|
||||
heroEntry,
|
||||
)
|
||||
|> IS.fromList;
|
||||
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Generic(aspectId)), ..._] =>
|
||||
let otherAspects = IS.delete(aspectId, activeAspects);
|
||||
|
||||
hero.liturgicalChants
|
||||
// If there is any liturgical chant with matching aspect
|
||||
// above SR 14 and no other aspect knowledges for that entry...
|
||||
|> IM.any((heroLiturgicalChant: ActivatableSkill.Dynamic.t) =>
|
||||
ActivatableSkill.Dynamic.valueToInt(
|
||||
heroLiturgicalChant.value,
|
||||
)
|
||||
> 14
|
||||
&& IM.lookup(
|
||||
heroLiturgicalChant.id,
|
||||
staticData.liturgicalChants,
|
||||
)
|
||||
|> O.option(true, ({LiturgicalChant.Static.aspects, _}) =>
|
||||
IS.member(aspectId, aspects)
|
||||
&& IS.disjoint(otherAspects, aspects)
|
||||
)
|
||||
)
|
||||
// ...prohibit removal
|
||||
|> (!);
|
||||
| _ => true
|
||||
};
|
||||
| CombatStyleCombination =>
|
||||
let activeArmedStyles = cache.armedCombatStylesCount;
|
||||
let activeUnarmedStyles = cache.unarmedCombatStylesCount;
|
||||
|
||||
let totalActive = activeArmedStyles + activeUnarmedStyles;
|
||||
|
||||
// default is 1 per group (armed/unarmed), but with this SA 1 more in
|
||||
// one group: maximum of 3, but max 2 per group. If max is reached,
|
||||
// this SA cannot be removed
|
||||
totalActive < 3 && (activeArmedStyles < 2 || activeUnarmedStyles < 2);
|
||||
| MagicStyleCombination =>
|
||||
// default is 1, but with this SA its 2. If it's 2 this SA is
|
||||
// neccessary and cannot be removed
|
||||
cache.magicalStylesCount < 2
|
||||
| Zugvoegel
|
||||
| JaegerinnenDerWeissenMaid
|
||||
| AnhaengerDesGueldenen =>
|
||||
switch (cache.blessedTradition) {
|
||||
| Some((_, _, blessedTradition)) =>
|
||||
hero.liturgicalChants
|
||||
// Filter liturgical chants...
|
||||
|> IM.mapMaybe(
|
||||
fun
|
||||
// ...so that only the active ones remain...
|
||||
| {ActivatableSkill.Dynamic.id, value: Active(_), _} =>
|
||||
// ...and replace with their static entry.
|
||||
IM.lookup(id, staticData.liturgicalChants)
|
||||
| _ => None,
|
||||
)
|
||||
// Check if there is any active entry that does not belong to the
|
||||
// active tradition...
|
||||
|> IM.any(({LiturgicalChant.Static.traditions, _}) =>
|
||||
IS.notElem(blessedTradition.numId, traditions)
|
||||
)
|
||||
// ...which would prohibit removal
|
||||
|> (!)
|
||||
| None => true
|
||||
}
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
let isStyleSpecialAbilityRemovalValid = (hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(specialAbility) =>
|
||||
Activatable_ExtendedStyle.isStyleValidToRemove(hero, specialAbility)
|
||||
};
|
||||
|
||||
/**
|
||||
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
|
||||
* level for an "increase entry", which is an entry that allows to get more
|
||||
* specific values or entries than usually allowed. `currentAmount` is the
|
||||
* current amount of specific values or entries used and `defaultAmount` the
|
||||
* default amount allowed.
|
||||
*/
|
||||
let getMinLevelForIncreaseEntry = (defaultAmount, currentAmount) =>
|
||||
currentAmount > defaultAmount ? Some(currentAmount - defaultAmount) : None;
|
||||
|
||||
/**
|
||||
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
|
||||
* level for a "decrease entry", which is an entry that disallows to get as many
|
||||
* specific values or entries as usually allowed. `currentAmount` is the current
|
||||
* amount of specific values or entries used and `defaultAmount` the default
|
||||
* amount allowed.
|
||||
*/
|
||||
let getMaxLevelForDecreaseEntry = (maxDecrease, current) =>
|
||||
I.max(0, maxDecrease - current);
|
||||
|
||||
let getEntrySpecificMinLevel =
|
||||
(cache, staticData: Static.t, hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Advantage.(
|
||||
switch (fromInt(staticAdvantage.id)) {
|
||||
| LargeSpellSelection =>
|
||||
hero
|
||||
|> ActivatableSkills.countActiveSkillEntries(Spells)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| ZahlreichePredigten =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| ZahlreicheVisionen =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
| Disadvantage(_) => None
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| Imitationszauberei =>
|
||||
hero.spells
|
||||
|> IM.countWith(({ActivatableSkill.Dynamic.id, _}) =>
|
||||
IM.lookup(id, staticData.spells)
|
||||
|> O.option(false, ({Spell.Static.gr, _}) =>
|
||||
gr === Id.Spell.Group.toInt(Spells)
|
||||
)
|
||||
)
|
||||
// Minimum level must be the number of active spells, if there are any
|
||||
|> O.ensure(count => count > 0)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getEntrySpecificMaxLevel = (cache, hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => None
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Disadvantage.(
|
||||
switch (fromInt(staticDisadvantage.id)) {
|
||||
| SmallSpellSelection =>
|
||||
hero
|
||||
|> ActivatableSkills.countActiveSkillEntries(Spells)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| WenigePredigten =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| WenigeVisionen =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| DunklesAbbildDerBuendnisgabe =>
|
||||
O.Infix.(hero.pact <&> (pact => pact.level))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let adjustMinLevelByDependencies =
|
||||
({Activatable_Dynamic.dependencies, _}, singleEntry, maybeMinLevel) =>
|
||||
L.foldr(
|
||||
(dependency: Activatable_Dynamic.dependency, maybeMinLevel) =>
|
||||
switch (dependency.level) {
|
||||
| Some(definedLevel) =>
|
||||
// get the level dependency from the object and ensure it's
|
||||
// greater than the current minimum level and that...
|
||||
definedLevel > O.sum(maybeMinLevel)
|
||||
// ...if the dependency defines options, too, the entry must match them
|
||||
// as well. A dependency without options is valid for all entries (in
|
||||
// case of calculating a minimum level)
|
||||
&& Dependencies.Activatable.areOptionDependenciesMatched(
|
||||
dependency,
|
||||
Activatable_Convert.singleWithIdToSingle(singleEntry),
|
||||
)
|
||||
? Some(definedLevel) : maybeMinLevel
|
||||
| None => maybeMinLevel
|
||||
},
|
||||
maybeMinLevel,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get minimum valid level.
|
||||
*/
|
||||
let getMinLevel =
|
||||
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) =>
|
||||
getEntrySpecificMinLevel(cache, staticData, hero, staticEntry)
|
||||
|> adjustMinLevelByDependencies(heroEntry, singleEntry);
|
||||
|
||||
let getMaxLevel = (cache, staticData, hero, staticEntry) => {
|
||||
let entrySpecificMaxLevel =
|
||||
getEntrySpecificMaxLevel(cache, hero, staticEntry);
|
||||
|
||||
let maxPossibleWithPrerequisites =
|
||||
Prerequisites.Validation.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry |> Activatable_Accessors.id |> Id.Activatable.toAll,
|
||||
Prerequisites.Activatable.getLevelPrerequisites(staticEntry),
|
||||
);
|
||||
|
||||
O.Infix.(
|
||||
O.liftM2(I.min, entrySpecificMaxLevel, maxPossibleWithPrerequisites)
|
||||
<|> entrySpecificMaxLevel
|
||||
<|> maxPossibleWithPrerequisites
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given active entry can be removed or changed in level.
|
||||
*/
|
||||
let isRemovalOrModificationValid =
|
||||
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) => {
|
||||
let minLevel =
|
||||
getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry);
|
||||
|
||||
let maxLevel = getMaxLevel(cache, staticData, hero, staticEntry);
|
||||
|
||||
{
|
||||
disabled:
|
||||
O.isSome(minLevel)
|
||||
|| !
|
||||
isNotRequiredByDependencies(
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
)
|
||||
|| !isStyleSpecialAbilityRemovalValid(hero, staticEntry)
|
||||
|| !
|
||||
isEntrySpecificRemovalValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
),
|
||||
maxLevel,
|
||||
minLevel,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
type t = {
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
disabled: bool,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
|
||||
* level for an "increase entry", which is an entry that allows to get more
|
||||
* specific values or entries than usually allowed. `currentAmount` is the
|
||||
* current amount of specific values or entries used and `defaultAmount` the
|
||||
* default amount allowed.
|
||||
*/
|
||||
let getMinLevelForIncreaseEntry: (int, int) => option(int);
|
||||
|
||||
/**
|
||||
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
|
||||
* level for a "decrease entry", which is an entry that disallows to get as many
|
||||
* specific values or entries as usually allowed. `currentAmount` is the current
|
||||
* amount of specific values or entries used and `defaultAmount` the default
|
||||
* amount allowed.
|
||||
*/
|
||||
let getMaxLevelForDecreaseEntry: (int, int) => int;
|
||||
|
||||
/**
|
||||
* `isRemovalOrModificationValid cache staticData hero staticEntry heroEntry
|
||||
* singleEntry` checks if the given active entry can be removed or if it's level
|
||||
* can be changed.
|
||||
*/
|
||||
let isRemovalOrModificationValid:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
Activatable_Convert.singleWithId
|
||||
) =>
|
||||
t;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* A set of values to validate `Weg der Schreiberin`.
|
||||
*/
|
||||
type matchingLanguagesScripts = {
|
||||
isEntryActiveRequiringMatch: bool,
|
||||
languagesWithMatchingScripts: list(int),
|
||||
scriptsWithMatchingLanguages: list(int),
|
||||
};
|
||||
|
||||
/**
|
||||
* The cache of computed values to optimize performance of activatable list
|
||||
* generation.
|
||||
*/
|
||||
type t = {
|
||||
startExperienceLevel: ExperienceLevel.t,
|
||||
combatStyleCombination: option(Activatable_Dynamic.t),
|
||||
armedCombatStylesCount: int,
|
||||
unarmedCombatStylesCount: int,
|
||||
magicalStylesCount: int,
|
||||
isBlessedStyleActive: bool,
|
||||
magicalStyleCombination: option(Activatable_Dynamic.t),
|
||||
dunklesAbbild: option(Activatable_Dynamic.t),
|
||||
activePactGiftsCount: int,
|
||||
matchingLanguagesScripts,
|
||||
validExtendedSpecialAbilities: list(int),
|
||||
requiredApplyToMagicalActions: bool,
|
||||
specialAbilityPairs:
|
||||
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))),
|
||||
combatTechniquePairs:
|
||||
Ley_IntMap.t((CombatTechnique.Static.t, option(Skill.Dynamic.t))),
|
||||
adventurePoints: AdventurePoints.categories,
|
||||
magicalTraditions: list(Tradition.Magical.fullTradition),
|
||||
blessedTradition: option(Tradition.Blessed.fullTradition),
|
||||
automaticAdvantages: list(int),
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
type single_with_id = {
|
||||
id : int;
|
||||
index : int;
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
(** A single active Activatable. *)
|
||||
|
||||
let single_to_single_with_id (x : 'a Activatable_Dynamic.t) index
|
||||
(s : Activatable_Dynamic.single) =
|
||||
{
|
||||
id = x.id;
|
||||
index;
|
||||
options = s.options;
|
||||
level = s.level;
|
||||
customCost = s.customCost;
|
||||
}
|
||||
|
||||
let hero_entry_to_singles (x : 'a Activatable_Dynamic.t) =
|
||||
x.active |> Ley_List.Index.imap (single_to_single_with_id x)
|
||||
|
||||
let single_with_id_to_single s =
|
||||
{
|
||||
Activatable_Dynamic.options = s.options;
|
||||
level = s.level;
|
||||
customCost = s.customCost;
|
||||
}
|
||||
|
||||
(* (** Converts the object generated by the list item to an object that can be
|
||||
inserted into an array of ActiveObjects.
|
||||
|
||||
@param obj The entry for which you want to convert the object.
|
||||
@param activate The object generated by the list item. *)
|
||||
let convertUIStateToActiveObject =
|
||||
(activate: Record<ActivatableActivationOptions>): Record<ActiveObject> => {
|
||||
const id = AAOA.id (activate)
|
||||
const selectOptionId1 = AAOA.selectOptionId1 (activate)
|
||||
const selectOptionId2 = AAOA.selectOptionId2 (activate)
|
||||
const selectOptionId3 = AAOA.selectOptionId3 (activate)
|
||||
const input = AAOA.input (activate)
|
||||
const level = AAOA.level (activate)
|
||||
const customCost = AAOA.customCost (activate)
|
||||
|
||||
return id === AdvantageId.HatredOf
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === DisadvantageId.PersonalityFlaw
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: or (fmap (elemF (List<number | string> (7, 8)))
|
||||
(selectOptionId1))
|
||||
? input
|
||||
: None,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === SpecialAbilityId.SkillSpecialization
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: alt<number | string> (input)
|
||||
(selectOptionId2),
|
||||
cost: customCost,
|
||||
})
|
||||
: isSome (input) && isSome (selectOptionId1)
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
sid3: selectOptionId2,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
: ActiveObject ({
|
||||
sid: alt<number | string> (input)
|
||||
(selectOptionId1),
|
||||
sid2: then (guard (isSome (input) || isSome (selectOptionId1)))
|
||||
(selectOptionId2),
|
||||
sid3: selectOptionId3,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
} *)
|
||||
|
||||
(* /**
|
||||
* Generates a list of ActiveObjects based on the given instance.
|
||||
*/
|
||||
export const convertActivatableToArray =
|
||||
(x: Record<ActivatableDependent>) =>
|
||||
pipe_ (
|
||||
x,
|
||||
ADA.active,
|
||||
imap (index => toActiveObjectWithId (index) (ADA.id (x)))
|
||||
) *)
|
||||
(* /**
|
||||
* Get all active items in an array.
|
||||
* @param state A state slice.
|
||||
*/
|
||||
export const getActiveFromState =
|
||||
foldr (pipe (convertActivatableToArray, append)) (empty)
|
||||
|
||||
export interface ActiveObjectAny extends ActiveObject {
|
||||
[key: string]: any
|
||||
} *)
|
||||
|
||||
let activatable_option_to_select_option_id = function
|
||||
| Id.Activatable.Option.Preset id -> Some id
|
||||
| CustomInput _ -> None
|
||||
@@ -0,0 +1,29 @@
|
||||
(** Convert specific values to be able to easier work with them.
|
||||
|
||||
This function provides some utility functions to convert some types from the
|
||||
state into smaller chuncks that are more usable for working with single
|
||||
entries. *)
|
||||
|
||||
type single_with_id = {
|
||||
id : int;
|
||||
index : int;
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
(** A single active Activatable. *)
|
||||
|
||||
val hero_entry_to_singles : 'a Activatable_Dynamic.t -> single_with_id list
|
||||
(** Converts an Activatable hero entry into a "flattened" list of it's
|
||||
activations. *)
|
||||
|
||||
val single_to_single_with_id :
|
||||
'a Activatable_Dynamic.t ->
|
||||
int ->
|
||||
Activatable_Dynamic.single ->
|
||||
single_with_id
|
||||
|
||||
val activatable_option_to_select_option_id :
|
||||
Id.Activatable.Option.t -> Id.Activatable.SelectOption.t option
|
||||
|
||||
val single_with_id_to_single : single_with_id -> Activatable_Dynamic.single
|
||||
@@ -0,0 +1,64 @@
|
||||
type single = {
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
|
||||
type dependency = {
|
||||
source : Id.Activatable.t;
|
||||
target : int OneOrMany.t;
|
||||
active : bool;
|
||||
options : Id.Activatable.SelectOption.t OneOrMany.t list;
|
||||
level : int option;
|
||||
}
|
||||
|
||||
type 'static t = {
|
||||
id : int;
|
||||
active : single list;
|
||||
dependencies : dependency list;
|
||||
static : 'static option;
|
||||
}
|
||||
|
||||
let empty static id = { id; active = []; dependencies = []; static }
|
||||
|
||||
let is_empty (x : 'a t) = Ley_List.null x.active && Ley_List.null x.dependencies
|
||||
|
||||
let is_active (x : 'a t) = Ley_List.Extra.notNull x.active
|
||||
|
||||
let is_active' x = Ley_Option.option false is_active x
|
||||
|
||||
module type S = sig
|
||||
type static
|
||||
|
||||
type nonrec t = static t
|
||||
(** The dynamic values in a character with a reference to the static values
|
||||
from the database. *)
|
||||
|
||||
val empty : static option -> int -> t
|
||||
(** [empty id] creates a new dynamic entry from an id. *)
|
||||
|
||||
val is_empty : t -> bool
|
||||
(** [is_empty x] checks if the passed dynamic entry is empty. *)
|
||||
|
||||
val is_active : t -> bool
|
||||
(** [is_active x] checks if the passed dynamic entry is active. *)
|
||||
|
||||
val is_active' : t option -> bool
|
||||
(** [is_active' x] checks if the passed optional dynamic entry is active. *)
|
||||
end
|
||||
|
||||
module Make (S : sig
|
||||
type static
|
||||
end) : S with type static = S.static = struct
|
||||
type static = S.static
|
||||
|
||||
type nonrec t = static t
|
||||
|
||||
let empty = empty
|
||||
|
||||
let is_empty = is_empty
|
||||
|
||||
let is_active = is_active
|
||||
|
||||
let is_active' = is_active'
|
||||
end
|
||||
@@ -0,0 +1,398 @@
|
||||
open Ley_Option;
|
||||
|
||||
/**
|
||||
* Returns a getter and a setter for the appropiate dependency list for the
|
||||
* passed style special ability or extended special ability.
|
||||
*/
|
||||
let getStyleDependenciesAcc = (style: SpecialAbility.Static.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.Group.fromInt(style.gr)) {
|
||||
| CombatStylesArmed
|
||||
| CombatStylesUnarmed
|
||||
| CombatExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.combatStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
combatStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| MagicalStyles
|
||||
| MagicalExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.magicalStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
magicalStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| BlessedStyles
|
||||
| KarmaExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.blessedStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
blessedStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| SkillStyles
|
||||
| SkillExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.skillStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
skillStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* If a style has multiple possible extended special abilities in one slot,
|
||||
* it has more options to offer. If such a slot is used and a new style
|
||||
* dependency with the used slot, but not multiple possibilities, is added,
|
||||
* this function will shift the use to the style dependency with the fixed
|
||||
* option instead, so there are more options left in the end.
|
||||
*/
|
||||
let moveActiveInListToNew = (newxs, x: Hero.styleDependency) =>
|
||||
switch (x.id, x.active) {
|
||||
// If the dependency has got a list of possible ids and is used, we check
|
||||
// if the used id is included in the new dependencies as a dependency
|
||||
// without a list of options to make more special abilities possible
|
||||
| (Many(_), Some(active)) =>
|
||||
// If a Some, it is the index in the list of new dependencies where we can
|
||||
// put the active dependency instead
|
||||
let index =
|
||||
Ley_List.findIndex(
|
||||
(newx: Hero.styleDependency) =>
|
||||
switch (newx.id) {
|
||||
| One(id) => id === active
|
||||
| Many(_) => false
|
||||
},
|
||||
newxs,
|
||||
);
|
||||
|
||||
switch (index) {
|
||||
| Some(index) => (
|
||||
Ley_List.Index.modifyAt(
|
||||
index,
|
||||
(newx: Hero.styleDependency) => {...newx, active: Some(active)},
|
||||
newxs,
|
||||
),
|
||||
{...x, active: None},
|
||||
)
|
||||
| None => (newxs, x)
|
||||
};
|
||||
| (Many(_), None)
|
||||
| (One(_), _) => (newxs, x)
|
||||
};
|
||||
|
||||
/**
|
||||
* `generateStyleDependencies heroSpecialAbilities styleSpecialAbility`
|
||||
* returns generated style dependencies for a style special ability.
|
||||
*/
|
||||
let generateStyleDependencies =
|
||||
(heroSpecialAbilities, styleSpecialAbility: SpecialAbility.Static.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (styleSpecialAbility.groupSpecific) {
|
||||
| CombatStyle({extended: (ext1, ext2, ext3), _})
|
||||
| GeneralStyle({extended: (ext1, ext2, ext3), _}) =>
|
||||
Ley_Option.Infix.(
|
||||
[ext1, ext2, ext3]
|
||||
|> Ley_List.map(({SpecialAbility.Static.Extended.id, _}) =>
|
||||
(
|
||||
{id: One(id), active: None, origin: styleSpecialAbility.id}: Hero.styleDependency
|
||||
)
|
||||
)
|
||||
|> (
|
||||
xs =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(styleSpecialAbility.id)) {
|
||||
// For this style, the user must choose between two special
|
||||
// abilities to be an extended special ability.
|
||||
| ScholarDesMagierkollegsZuHoningen =>
|
||||
Ley_IntMap.lookup(
|
||||
styleSpecialAbility.id,
|
||||
heroSpecialAbilities,
|
||||
)
|
||||
>>= (
|
||||
(x: Activatable_Dynamic.t) =>
|
||||
x.active |> Ley_Option.listToOption
|
||||
)
|
||||
>>= (x => Ley_List.Safe.atMay(x.options, 1))
|
||||
|> (
|
||||
fun
|
||||
| Some(Preset(SpecialAbility(id))) => [
|
||||
{
|
||||
Hero.id: One(id),
|
||||
active: None,
|
||||
origin: styleSpecialAbility.id,
|
||||
},
|
||||
...xs,
|
||||
]
|
||||
| Some(_)
|
||||
| None => xs
|
||||
)
|
||||
| _ => xs
|
||||
}
|
||||
)
|
||||
)
|
||||
|> Ley_Option.return
|
||||
)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability.
|
||||
*/
|
||||
let addStyleExtendedSpecialAbilityDependencies = (styleSpecialAbility, hero) =>
|
||||
liftM2(
|
||||
((get, set), newxs) =>
|
||||
hero
|
||||
|> get
|
||||
|> Ley_List.mapAccumL(moveActiveInListToNew, newxs)
|
||||
|> (((xs1, xs2)) => xs1 @ xs2)
|
||||
|> set(hero),
|
||||
getStyleDependenciesAcc(styleSpecialAbility),
|
||||
generateStyleDependencies(hero.specialAbilities, styleSpecialAbility),
|
||||
)
|
||||
|> Ley_Option.fromOption(hero);
|
||||
|
||||
/**
|
||||
* Get the index in the list of style dependencies where there is a free slot
|
||||
* for the passed extended special ability. Prefers fixed ID slots.
|
||||
*/
|
||||
let getIndexOfFreeDependency =
|
||||
(
|
||||
extendedSpecialAbility: SpecialAbility.Static.t,
|
||||
xs: list(Hero.styleDependency),
|
||||
) =>
|
||||
Ley_Option.Infix.
|
||||
// Prefer dependency with fixed option
|
||||
(
|
||||
Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.id) {
|
||||
| One(id) => id === extendedSpecialAbility.id
|
||||
| Many(_) => false
|
||||
},
|
||||
xs,
|
||||
)
|
||||
// Otherwise search for dependency with dynamic option
|
||||
<|> Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.id) {
|
||||
| One(_) => false
|
||||
| Many(ids) => Ley_List.elem(extendedSpecialAbility.id, ids)
|
||||
},
|
||||
xs,
|
||||
)
|
||||
|> Ley_Option.fromOption(-1)
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds the passed extended special ability to a free slot of a style
|
||||
* dependency.
|
||||
*/
|
||||
let addExtendedSpecialAbilityDependency =
|
||||
(extendedSpecialAbility, hero: Hero.t) =>
|
||||
extendedSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> (
|
||||
xs =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getIndexOfFreeDependency(extendedSpecialAbility, xs),
|
||||
(d: Hero.styleDependency) =>
|
||||
{...d, active: Some(extendedSpecialAbility.id)},
|
||||
xs,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability or adds the passed special ability's ID to a free slot of a
|
||||
* style dependency if it is an extended special ability.
|
||||
*/
|
||||
let addAllStyleRelatedDependencies = (specialAbility, hero) =>
|
||||
hero
|
||||
|> addStyleExtendedSpecialAbilityDependencies(specialAbility)
|
||||
|> addExtendedSpecialAbilityDependency(specialAbility);
|
||||
|
||||
/**
|
||||
* `getSplittedRemainingAndToRemove` takes a style special ability's ID and
|
||||
* the list of style dependencies and returns `(toBeRemoved, remaining)`,
|
||||
* where `toBeRemoved` is the list of style dependencies that have to be
|
||||
* removed when removing the style special ability and `remaining` is the list
|
||||
* of remaining style dependencies after the removal.
|
||||
*/
|
||||
let getSplittedRemainingAndToRemove = (id, xs) =>
|
||||
Ley_List.partition((x: Hero.styleDependency) => x.origin === id, xs);
|
||||
|
||||
/**
|
||||
* Returns an index of a free style dependency so that the currently used
|
||||
* style dependency can be removed and the use can be moved.
|
||||
*/
|
||||
let getAlternativeIndex = (toBeRemoved: Hero.styleDependency, remainings) =>
|
||||
switch (toBeRemoved.active) {
|
||||
| Some(activeId) =>
|
||||
remainings
|
||||
|> Ley_List.findIndex((remaining: Hero.styleDependency) =>
|
||||
(
|
||||
switch (remaining.id) {
|
||||
| One(id) => id === activeId
|
||||
| Many(ids) => Ley_List.elem(activeId, ids)
|
||||
}
|
||||
)
|
||||
&& Ley_Option.isNone(remaining.active)
|
||||
)
|
||||
|> Ley_Option.fromOption(-1)
|
||||
| None => (-1)
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability.
|
||||
*/
|
||||
let removeStyleExtendedSpecialAbilityDependencies =
|
||||
(styleSpecialAbility, hero) =>
|
||||
styleSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|
||||
|> (
|
||||
((dependenciesToRemove, remainingDependencies)) =>
|
||||
dependenciesToRemove
|
||||
|> Ley_List.filter((x: Hero.styleDependency) =>
|
||||
Ley_Option.isSome(x.active)
|
||||
)
|
||||
|> Ley_List.foldr(
|
||||
(toRemove: Hero.styleDependency, remainings) =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getAlternativeIndex(toRemove, remainings),
|
||||
(remaining: Hero.styleDependency) =>
|
||||
{...remaining, active: toRemove.active},
|
||||
remainings,
|
||||
),
|
||||
remainingDependencies,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the index in the list of style dependencies where there is a used slot
|
||||
* for the passed extended special ability.
|
||||
*/
|
||||
let getIndexOfUsedDependency =
|
||||
(
|
||||
extendedSpecialAbility: SpecialAbility.Static.t,
|
||||
xs: list(Hero.styleDependency),
|
||||
) =>
|
||||
Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.active) {
|
||||
| Some(id) => id === extendedSpecialAbility.id
|
||||
| None => false
|
||||
},
|
||||
xs,
|
||||
)
|
||||
|> Ley_Option.fromOption(-1);
|
||||
|
||||
/**
|
||||
* Removes the passed extended special ability from a used slot of a style
|
||||
* dependency.
|
||||
*/
|
||||
let removeExtendedSpecialAbilityDependency =
|
||||
(extendedSpecialAbility, hero: Hero.t) =>
|
||||
extendedSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> (
|
||||
xs =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getIndexOfUsedDependency(extendedSpecialAbility, xs),
|
||||
(d: Hero.styleDependency) => {...d, active: None},
|
||||
xs,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability or removes the passed special ability's ID from a
|
||||
* used slot of a style dependency if it is an extended special ability.
|
||||
*/
|
||||
let removeAllStyleRelatedDependencies = (specialAbility, hero) =>
|
||||
hero
|
||||
|> removeStyleExtendedSpecialAbilityDependencies(specialAbility)
|
||||
|> removeExtendedSpecialAbilityDependency(specialAbility);
|
||||
|
||||
/**
|
||||
* Return flat array of available extended special abilities' IDs.
|
||||
*/
|
||||
let getAvailableExtendedSpecialAbilities = styleDependencies =>
|
||||
styleDependencies
|
||||
|> Ley_List.concatMap((x: Hero.styleDependency) =>
|
||||
switch (x.active, x.id) {
|
||||
| (None, One(id)) => [id]
|
||||
| (None, Many(ids)) => ids
|
||||
| (Some(_), _) => []
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculates a list of available Extended Special Abilties. The availability is
|
||||
* only based on bought Style Special Abilities, so (other) prerequisites have
|
||||
* to be checked separately.
|
||||
*/
|
||||
let getAllAvailableExtendedSpecialAbilities =
|
||||
Ley_List.concatMap(getAvailableExtendedSpecialAbilities);
|
||||
|
||||
/**
|
||||
* Checks if the passed special ability is a style and if it is valid to
|
||||
* remove based on registered active extended special abilities.
|
||||
*/
|
||||
let isStyleValidToRemove = (hero, styleSpecialAbility) =>
|
||||
styleSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(true, ((get, _)) =>
|
||||
hero
|
||||
|> get
|
||||
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|
||||
|> (
|
||||
((dependenciesToRemove, remainingDependencies)) =>
|
||||
dependenciesToRemove
|
||||
// Check for every used dependency slot...
|
||||
|> Ley_List.filter((x: Hero.styleDependency) =>
|
||||
Ley_Option.isSome(x.active)
|
||||
)
|
||||
// ...if it can be moved to a different dependency
|
||||
|> Ley_List.all(x =>
|
||||
getAlternativeIndex(x, remainingDependencies) > (-1)
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Working with Style Special Abilties and their Extended Special Abilties.
|
||||
*
|
||||
* This module provides some functions for working with Style and Extended
|
||||
* Special Abilties, how to manage their dependencies and what is allowed to do
|
||||
* in that context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability or adds the passed special ability's ID to a free slot of a
|
||||
* style dependency if it is an extended special ability.
|
||||
*/
|
||||
let addAllStyleRelatedDependencies:
|
||||
(SpecialAbility.Static.t, Hero.t) => Hero.t;
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability or removes the passed special ability's ID from a
|
||||
* used slot of a style dependency if it is an extended special ability.
|
||||
*/
|
||||
let removeAllStyleRelatedDependencies:
|
||||
(SpecialAbility.Static.t, Hero.t) => Hero.t;
|
||||
|
||||
/**
|
||||
* Calculates a list of available Extended Special Abilties. The availability is
|
||||
* only based on bought Style Special Abilities, so (other) prerequisites have
|
||||
* to be checked separately.
|
||||
*/
|
||||
let getAllAvailableExtendedSpecialAbilities:
|
||||
list(list(Hero.styleDependency)) => list(int);
|
||||
|
||||
/**
|
||||
* Checks if the passed special ability is a style and if it is valid to
|
||||
* remove based on registered active extended special abilities.
|
||||
*/
|
||||
let isStyleValidToRemove: (Hero.t, SpecialAbility.Static.t) => bool;
|
||||
@@ -0,0 +1,313 @@
|
||||
module IM = Ley_IntMap;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
type t = {
|
||||
id: int,
|
||||
name: string,
|
||||
apValue: option(OneOrMany.t(int)),
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
selectOptions: list(SelectOption.t),
|
||||
heroEntry: option(Activatable_Dynamic.t),
|
||||
staticEntry: Activatable.t,
|
||||
customCostDisabled: bool,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
type result =
|
||||
| Valid(t)
|
||||
| Invalid(Activatable.t);
|
||||
|
||||
let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) =>
|
||||
Activatable_Modifier.modifyByLevel(
|
||||
3,
|
||||
IM.lookup(Id.Advantage.toInt(advantageId), hero.advantages),
|
||||
IM.lookup(Id.Disadvantage.toInt(disadvantageId), hero.disadvantages),
|
||||
);
|
||||
|
||||
let modifyOtherOptions =
|
||||
(cache, staticData: Static.t, hero, staticEntry, maybeHeroEntry, base) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => Some(base)
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Disadvantage.(
|
||||
switch (fromInt(staticDisadvantage.id)) {
|
||||
| WenigePredigten as id
|
||||
| WenigeVisionen as id =>
|
||||
Some({
|
||||
...base,
|
||||
maxLevel:
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
switch (id) {
|
||||
| WenigePredigten => Predigten
|
||||
| _ => Visionen
|
||||
},
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|
||||
|> O.ensure((<)(0)),
|
||||
})
|
||||
|
||||
| SmallSpellSelection =>
|
||||
Some({
|
||||
...base,
|
||||
maxLevel:
|
||||
ActivatableSkills.countActiveSkillEntries(Spells, hero)
|
||||
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|
||||
|> O.ensure((<)(0)),
|
||||
})
|
||||
|
||||
| _ => Some(base)
|
||||
}
|
||||
)
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| CraftInstruments =>
|
||||
let getSkillValue = skill =>
|
||||
hero.skills
|
||||
|> IM.lookup(Id.Skill.toInt(skill))
|
||||
|> Skill.Dynamic.getValueDef;
|
||||
|
||||
// Sum of SR of Woodworking and Metalworking must be at least 12
|
||||
getSkillValue(Woodworking) + getSkillValue(Metalworking) >= 12
|
||||
? Some(base) : None;
|
||||
|
||||
| Hunter =>
|
||||
EntryGroups.CombatTechnique.getFromGroup(
|
||||
Ranged,
|
||||
cache.combatTechniquePairs,
|
||||
)
|
||||
|> IM.any(
|
||||
fun
|
||||
// At least one ranged combat technique must be on CtR 10 or higher
|
||||
| (_, Some((heroEntry: Skill.Dynamic.t))) =>
|
||||
heroEntry.value >= 10
|
||||
| _ => false,
|
||||
)
|
||||
? Some(base) : None
|
||||
|
||||
| TraditionGuildMages
|
||||
| TraditionWitches
|
||||
| TraditionElves
|
||||
| TraditionDruids
|
||||
| TraditionIllusionist
|
||||
| TraditionQabalyaMage
|
||||
| TraditionGeoden
|
||||
| TraditionZauberalchimisten
|
||||
| TraditionSchelme
|
||||
| TraditionBrobimGeoden =>
|
||||
// Only one tradition allowed
|
||||
L.null(cache.magicalTraditions) ? Some(base) : None
|
||||
|
||||
| PropertyKnowledge
|
||||
| AspectKnowledge =>
|
||||
switch (staticSpecialAbility.apValue) {
|
||||
| Some(PerLevel(values)) =>
|
||||
let index =
|
||||
O.option(
|
||||
0,
|
||||
({active, _}: Activatable_Dynamic.t) => active |> L.length,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
O.Infix.(
|
||||
L.Safe.atMay(values, index)
|
||||
<&> (apValue => {...base, apValue: Some(One(apValue))})
|
||||
);
|
||||
| Some(Flat(_))
|
||||
| None => None
|
||||
}
|
||||
|
||||
| TraditionChurchOfPraios
|
||||
| TraditionChurchOfRondra
|
||||
| TraditionChurchOfBoron
|
||||
| TraditionChurchOfHesinde
|
||||
| TraditionChurchOfPhex
|
||||
| TraditionChurchOfPeraine
|
||||
| TraditionChurchOfEfferd
|
||||
| TraditionChurchOfTravia
|
||||
| TraditionChurchOfFirun
|
||||
| TraditionChurchOfTsa
|
||||
| TraditionChurchOfIngerimm
|
||||
| TraditionChurchOfRahja
|
||||
| TraditionCultOfTheNamelessOne
|
||||
| TraditionChurchOfAves
|
||||
| TraditionChurchOfIfirn
|
||||
| TraditionChurchOfKor
|
||||
| TraditionChurchOfNandus
|
||||
| TraditionChurchOfSwafnir
|
||||
| TraditionCultOfNuminoru =>
|
||||
// Only one tradition allowed
|
||||
O.isNone(cache.blessedTradition) ? Some(base) : None
|
||||
|
||||
| Recherchegespuer =>
|
||||
O.Infix.(
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(Wissensdurst))
|
||||
<&> (({active, _}) => active)
|
||||
>>= O.listToOption
|
||||
>>= (({options, _}) => O.listToOption(options))
|
||||
>>= (
|
||||
fun
|
||||
| Preset(Skill(skillId)) =>
|
||||
IM.lookup(skillId, staticData.skills)
|
||||
| _ => None
|
||||
)
|
||||
>>= (
|
||||
({ic, _}) =>
|
||||
switch (staticSpecialAbility.apValue) {
|
||||
| Some(PerLevel(values)) =>
|
||||
values
|
||||
|> L.map((+)(IC.getApForActivatation(ic)))
|
||||
|> (
|
||||
sumValues =>
|
||||
{...base, apValue: Some(Many(sumValues))} |> O.return
|
||||
)
|
||||
| Some(Flat(_))
|
||||
| None => None
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
| PredigtDerGemeinschaft
|
||||
| PredigtDerZuversicht
|
||||
| PredigtDesGottvertrauens
|
||||
| PredigtDesWohlgefallens
|
||||
| PredigtWiderMissgeschicke =>
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|
||||
|> Activatable_Accessors.isActiveM;
|
||||
|
||||
let max =
|
||||
getSermonOrVisionCountMax(
|
||||
hero,
|
||||
ZahlreichePredigten,
|
||||
WenigePredigten,
|
||||
);
|
||||
|
||||
let isLessThanMax =
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
< max;
|
||||
|
||||
isAdvActive(Prediger) && isLessThanMax || isAdvActive(Blessed)
|
||||
? Some(base) : None;
|
||||
|
||||
| VisionDerBestimmung
|
||||
| VisionDerEntrueckung
|
||||
| VisionDerGottheit
|
||||
| VisionDesSchicksals
|
||||
| VisionDesWahrenGlaubens =>
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|
||||
|> Activatable_Accessors.isActiveM;
|
||||
|
||||
let max =
|
||||
getSermonOrVisionCountMax(hero, ZahlreicheVisionen, WenigeVisionen);
|
||||
|
||||
let isLessThanMax =
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
< max;
|
||||
|
||||
isAdvActive(Visionaer) && isLessThanMax || isAdvActive(Blessed)
|
||||
? Some(base) : None;
|
||||
|
||||
| DunklesAbbildDerBuendnisgabe =>
|
||||
O.Infix.(
|
||||
hero.pact
|
||||
>>= (
|
||||
({level, _}) =>
|
||||
level > 0 ? Some({...base, maxLevel: Some(level)}) : None
|
||||
)
|
||||
)
|
||||
|
||||
| TraditionArcaneBard
|
||||
| TraditionArcaneDancer
|
||||
| TraditionIntuitiveMage
|
||||
| TraditionSavant
|
||||
| TraditionAnimisten =>
|
||||
cache.adventurePoints.spentOnMagicalAdvantages <= 25
|
||||
&& cache.adventurePoints.spentOnMagicalDisadvantages <= 25
|
||||
&& L.null(cache.magicalTraditions)
|
||||
? Some(base) : None
|
||||
|
||||
| _ => Some(base)
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getInactive = (cache, staticData, hero, staticEntry, maybeHeroEntry) => {
|
||||
let maxLevel =
|
||||
Activatable_Inactive_Validation.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
let isAdditionValid =
|
||||
Activatable_Inactive_Validation.isAdditionValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
maxLevel,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
if (isAdditionValid) {
|
||||
O.Infix.(
|
||||
Activatable_Inactive_SelectOptions.getAvailableSelectOptions(
|
||||
staticData,
|
||||
hero,
|
||||
cache.magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
)
|
||||
<&> (
|
||||
selectOptions => {
|
||||
id: Activatable_Accessors.id'(staticEntry),
|
||||
name: Activatable_Accessors.name(staticEntry),
|
||||
apValue: Activatable_Accessors.apValue'(staticEntry),
|
||||
minLevel: None,
|
||||
maxLevel,
|
||||
selectOptions,
|
||||
heroEntry: maybeHeroEntry,
|
||||
staticEntry,
|
||||
customCostDisabled: true,
|
||||
isAutomatic:
|
||||
switch (staticEntry) {
|
||||
| Advantage({id, _}) => L.elem(id, cache.automaticAdvantages)
|
||||
| Disadvantage(_)
|
||||
| SpecialAbility(_) => false
|
||||
},
|
||||
}
|
||||
)
|
||||
>>= (
|
||||
base =>
|
||||
modifyOtherOptions(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
base,
|
||||
)
|
||||
)
|
||||
|> O.option(Invalid(staticEntry), base => Valid(base))
|
||||
);
|
||||
} else {
|
||||
Invalid(staticEntry);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
type t = {
|
||||
id: int,
|
||||
name: string,
|
||||
apValue: option(OneOrMany.t(int)),
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
selectOptions: list(SelectOption.t),
|
||||
heroEntry: option(Activatable_Dynamic.t),
|
||||
staticEntry: Activatable.t,
|
||||
customCostDisabled: bool,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
type result =
|
||||
| Valid(t)
|
||||
| Invalid(Activatable.t);
|
||||
|
||||
/**
|
||||
* `getInactive cache staticData hero staticEntry maybeHeroEntry` validates an
|
||||
* inactive entry, filters possible options and then either returns a valid
|
||||
* entry with additional and filtered options or an invalid entry.
|
||||
*
|
||||
* This can be used to display valid as well as invalid entries in a single
|
||||
* list.
|
||||
*/
|
||||
let getInactive:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
result;
|
||||
@@ -0,0 +1,699 @@
|
||||
open SelectOption;
|
||||
open Ley_Option;
|
||||
open Activatable_Accessors;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
module F = Ley_Function;
|
||||
module SO = SelectOption;
|
||||
module SOM = SelectOption.Map;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
module T = Ley_Transducer;
|
||||
module IM = Ley_IntMap;
|
||||
module IS = Ley_IntSet;
|
||||
|
||||
open T;
|
||||
|
||||
/**
|
||||
* Test if the id of the passed select option is activated for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
let isNotActive = maybeHeroEntry =>
|
||||
maybeHeroEntry
|
||||
|> O.option([], getActiveSelectOptions1)
|
||||
|> ((activeSelections, {id, _}: SO.t) => L.notElem(id, activeSelections));
|
||||
|
||||
/**
|
||||
* Test if a select option is not activated more than once for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
let areNoSameActive = maybeHeroEntry =>
|
||||
maybeHeroEntry
|
||||
|> O.option([], getActiveSelectOptions1)
|
||||
|> (
|
||||
(activeSelections, {id, _}: SO.t) => L.countMax(id, 1, activeSelections)
|
||||
);
|
||||
|
||||
/**
|
||||
* `isNotRequired otherActivatables maybeHeroEntry so` returns if the id of the
|
||||
* passed select option `so` is required for the passed Activatable
|
||||
* `maybeHeroEntry`. `otherActivatables` should be a map of the Activatables
|
||||
* from the same group ()
|
||||
*/
|
||||
let isNotRequired = (otherActivatables, maybeHeroEntry) =>
|
||||
maybeHeroEntry
|
||||
|> O.option(
|
||||
[],
|
||||
Dependencies.Flatten.getRequiredSelectOptions1(otherActivatables),
|
||||
)
|
||||
|> (
|
||||
(applicableOptions, {id, _}: SO.t) =>
|
||||
L.all(
|
||||
fun
|
||||
| OneOrMany.One(option) => Id.Activatable.SelectOption.(option != id)
|
||||
| Many(options) => L.notElem(id, options),
|
||||
applicableOptions,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* `isNotRequiredNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if the id of the passed select option is neither required nor activated
|
||||
* for the passed `ActivatableDependent`.
|
||||
*/
|
||||
let isNotRequiredNotActive = (otherActivatables, maybeHeroEntry) => {
|
||||
let isNoActiveSelection = isNotActive(maybeHeroEntry);
|
||||
let isNoRequiredSelection =
|
||||
isNotRequired(otherActivatables, maybeHeroEntry);
|
||||
|
||||
so => isNoActiveSelection(so) && isNoRequiredSelection(so);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available and if their
|
||||
* prerequisites are met.
|
||||
*/
|
||||
let isValid = (staticData: Static.t, hero: Hero.t, entryId, fold) =>
|
||||
fold
|
||||
>>~ EntryAvailability.isAvailableNull(
|
||||
(x: SelectOption.t) => x.src,
|
||||
staticData.publications,
|
||||
hero.rules,
|
||||
)
|
||||
>>~ (
|
||||
x =>
|
||||
x.prerequisites
|
||||
|> Prerequisite.Collection.ByLevel.getFirstLevel
|
||||
|> Ley_List.map(Prerequisite.General.unify)
|
||||
|> Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
entryId,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available, if their
|
||||
* prerequisites are met, and if its neither active nor required.
|
||||
*/
|
||||
let isNoGenericRequiredOrActiveSelection =
|
||||
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
|
||||
fold
|
||||
|> isValid(staticData, hero, entryId)
|
||||
>>~ isNotRequiredNotActive(otherActivatables, maybeHeroEntry);
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available, if their
|
||||
* prerequisites are met, and if its not required.
|
||||
*/
|
||||
let isNoGenericRequiredSelection =
|
||||
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
|
||||
fold
|
||||
|> isValid(staticData, hero, entryId)
|
||||
>>~ isNotRequired(otherActivatables, maybeHeroEntry);
|
||||
|
||||
/**
|
||||
* From a list of spell select options, only return the ones that match the
|
||||
* predicate.
|
||||
*/
|
||||
let filterSpellSelectOptions = (pred, fold) =>
|
||||
fold
|
||||
>>~ (
|
||||
(x: SelectOption.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (x.staticEntry) {
|
||||
| Some(Spell(spell)) => Some(spell)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|> O.option(false, pred)
|
||||
);
|
||||
|
||||
let getAvailableSelectOptionsTransducer =
|
||||
(
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
) => {
|
||||
// Prefill some parameters
|
||||
let isValidShort = isValid(staticData, hero);
|
||||
|
||||
// Prefill some parameters
|
||||
let isNoGenericRequiredOrActiveSelectionShort =
|
||||
isNoGenericRequiredOrActiveSelection(staticData, hero, maybeHeroEntry);
|
||||
|
||||
// Prefill some parameters
|
||||
let isNoGenericRequiredSelectionShort =
|
||||
isNoGenericRequiredSelection(staticData, hero, maybeHeroEntry);
|
||||
|
||||
switch ((staticEntry: Activatable.t)) {
|
||||
| Advantage(staticAdvantage) =>
|
||||
let id = Id.All.Advantage(staticAdvantage.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.advantages, id);
|
||||
|
||||
let isNoRequiredSelection =
|
||||
isNoGenericRequiredSelectionShort(hero.advantages, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(staticAdvantage.id)) {
|
||||
| ExceptionalSkill =>
|
||||
Some(
|
||||
fold =>
|
||||
// There shouldnt be more than two activations for the same skill
|
||||
fold |> isNoRequiredSelection >>~ areNoSameActive(maybeHeroEntry),
|
||||
)
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
let id = Id.All.Disadvantage(staticDisadvantage.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.disadvantages, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(staticDisadvantage.id)) {
|
||||
| PersonalityFlaw =>
|
||||
// List of unique active select options
|
||||
let uniqueSelections =
|
||||
O.option(
|
||||
[],
|
||||
heroEntry =>
|
||||
heroEntry
|
||||
|> mapActiveOptions1(
|
||||
fun
|
||||
| Preset(Generic(id)) => Some(id)
|
||||
| _ => None,
|
||||
),
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
// Checks if the passed id either matches the passed select option's id
|
||||
// and is already active.
|
||||
let isInfiniteActive = (id, selectOption: SO.t) =>
|
||||
selectOption.id == Generic(id) && L.elem(id, uniqueSelections);
|
||||
|
||||
// Checks if the passed select option is Prejudice and if Prejudice is
|
||||
// already active.
|
||||
let isPrejudiceAndActive = isInfiniteActive(7);
|
||||
|
||||
// Checks if the passed select option is Unworldly and if Unworldly is
|
||||
// already active.
|
||||
let isUnworldlyAndActive = isInfiniteActive(8);
|
||||
|
||||
// Checks if the passed select option is not active, not required and if
|
||||
// there is a maximum of one other unique select option of this entry
|
||||
// active.
|
||||
let isNotActiveAndMaxNotReached = selectOption =>
|
||||
isNotRequiredNotActive(
|
||||
hero.disadvantages,
|
||||
maybeHeroEntry,
|
||||
selectOption,
|
||||
)
|
||||
&& L.lengthMax(1, uniqueSelections);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isValidShort(Disadvantage(staticDisadvantage.id))
|
||||
>>~ (
|
||||
x =>
|
||||
// If Prejudice is active, it can be activated multiple times
|
||||
isPrejudiceAndActive(x)
|
||||
// If Unworldly is active, it can be activated multiple times
|
||||
|| isUnworldlyAndActive(x)
|
||||
// If the select option has not been activated yet, there must be
|
||||
// a maximum of one other unique activations, since only a maximum
|
||||
// of two unique is allowed
|
||||
|| isNotActiveAndMaxNotReached(x)
|
||||
),
|
||||
);
|
||||
| NegativeTrait
|
||||
| Maimed => Some(isNoRequiredOrActiveSelection)
|
||||
| Incompetent =>
|
||||
// Checks if the advantage with the passed id is active
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages) |> isActiveM;
|
||||
|
||||
// Checks if the passed select option does not represent a social skill
|
||||
let isNotSocialSkill = (so: SO.t) =>
|
||||
switch (so.id) {
|
||||
| Skill(id) =>
|
||||
IM.lookup(id, staticData.skills)
|
||||
|> O.any((skill: Skill.Static.t) =>
|
||||
skill.gr === Id.Skill.Group.toInt(Social)
|
||||
)
|
||||
|> (!)
|
||||
| _ => true
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ (
|
||||
x =>
|
||||
// Socially Adaptable and Inspire Confidence
|
||||
// require no Incompetence in social skills
|
||||
isAdvActive(SociallyAdaptable)
|
||||
|| isAdvActive(InspireConfidence)
|
||||
? isNotSocialSkill(x) : true
|
||||
),
|
||||
);
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
let id = Id.All.SpecialAbility(staticSpecialAbility.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.specialAbilities, id);
|
||||
|
||||
let isNoRequiredSelection =
|
||||
isNoGenericRequiredSelectionShort(hero.specialAbilities, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(staticSpecialAbility.id)) {
|
||||
| SkillSpecialization =>
|
||||
let isFirstSpecializationValid = id =>
|
||||
IM.lookup(id, hero.skills)
|
||||
|> O.option(false, (skill: Skill.Dynamic.t)
|
||||
// Skill Rating must be at least 6 for 1st specialization
|
||||
=> skill.value >= 6);
|
||||
|
||||
let isAdditionalSpecializationValid =
|
||||
(counter, selectOption: SO.t, id) =>
|
||||
liftM2(
|
||||
(skill: Skill.Dynamic.t, activeApps) =>
|
||||
// Maximum of three is allowed
|
||||
L.lengthMax(2, activeApps)
|
||||
// Skill Rating must be at least 6/12/18 for 1st/2nd/3rd specialization
|
||||
&& skill.value >= (L.length(activeApps) + 1)
|
||||
* 6,
|
||||
IM.lookup(id, hero.skills),
|
||||
SOM.lookup(selectOption.id, counter),
|
||||
)
|
||||
|> O.dis;
|
||||
|
||||
let counter = O.Infix.(maybeHeroEntry <&> getActiveOptions2Map);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredSelection
|
||||
>>~ (
|
||||
switch (counter) {
|
||||
// if counter is available, maybeHeroEntry must be a Some and thus there
|
||||
// can be active selections
|
||||
| Some(counter) => (
|
||||
selectOption =>
|
||||
switch (selectOption.id) {
|
||||
| Skill(id) =>
|
||||
SOM.member(selectOption.id, counter)
|
||||
? isAdditionalSpecializationValid(
|
||||
counter,
|
||||
selectOption,
|
||||
id,
|
||||
)
|
||||
: isFirstSpecializationValid(id)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
// otherwise we only need to check if the skill rating is at least 6, as
|
||||
// there can't be an activated selection then.
|
||||
| None => (
|
||||
selectOption =>
|
||||
switch (selectOption.id) {
|
||||
| Skill(id) => isFirstSpecializationValid(id)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
<&~ (
|
||||
(selectOption: SO.t) => {
|
||||
let maybeCurrentCount =
|
||||
O.Infix.(counter >>= SOM.lookup(selectOption.id));
|
||||
|
||||
selectOption
|
||||
// Increase cost if there are active specializations
|
||||
// for the same skill
|
||||
|> O.liftDef(selectOption =>
|
||||
O.liftM2(
|
||||
(currentCount, selectOptionCost) =>
|
||||
{
|
||||
...selectOption,
|
||||
apValue:
|
||||
Some(
|
||||
selectOptionCost * (L.length(currentCount) + 1),
|
||||
),
|
||||
},
|
||||
maybeCurrentCount,
|
||||
selectOption.apValue,
|
||||
)
|
||||
)
|
||||
|> O.liftDef((selectOption: SO.t) =>
|
||||
Ley_Option.Infix.(
|
||||
(
|
||||
switch (selectOption.staticEntry) {
|
||||
| Some(Skill(skill)) => Some(skill)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
<&> (
|
||||
skill =>
|
||||
skill.applications
|
||||
|> IM.elems
|
||||
|> L.filter(
|
||||
(application: Skill.Static.Application.t) => {
|
||||
// An application can only be selected once
|
||||
let isInactive =
|
||||
O.all(
|
||||
L.notElem(
|
||||
Id.Activatable.Option.Preset(
|
||||
Generic(application.id),
|
||||
),
|
||||
),
|
||||
maybeCurrentCount,
|
||||
);
|
||||
|
||||
// New applications must have valid prerequisites
|
||||
let hasValidPrerequisites =
|
||||
Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
id,
|
||||
switch (application.prerequisite) {
|
||||
| Some(prerequisite) => [
|
||||
{
|
||||
value: Activatable(prerequisite),
|
||||
displayOption: Generate,
|
||||
},
|
||||
]
|
||||
| None => []
|
||||
},
|
||||
);
|
||||
|
||||
isInactive && hasValidPrerequisites;
|
||||
})
|
||||
|> (
|
||||
applications => {
|
||||
...selectOption,
|
||||
applications: Some(applications),
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
| TraditionGuildMages =>
|
||||
// Filter all spells so that only spells are included that cant be
|
||||
// familiar spells
|
||||
Some(
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.disjoint(
|
||||
spell.traditions,
|
||||
Ley_IntSet.fromList([
|
||||
Id.MagicalTradition.toInt(General),
|
||||
Id.MagicalTradition.toInt(GuildMages),
|
||||
]),
|
||||
)
|
||||
),
|
||||
)
|
||||
| PropertyKnowledge as id
|
||||
| AspectKnowledge as id =>
|
||||
let availableIds =
|
||||
switch (id) {
|
||||
| PropertyKnowledge =>
|
||||
Spells.PropertyKnowledge.getAvailableProperties(
|
||||
staticData.spells,
|
||||
hero.spells,
|
||||
)
|
||||
| AspectKnowledge =>
|
||||
LiturgicalChants.AspectKnowledge.getAvailableAspects(
|
||||
staticData.liturgicalChants,
|
||||
hero.liturgicalChants,
|
||||
)
|
||||
// Never happens
|
||||
| _ => []
|
||||
};
|
||||
|
||||
let isValidId = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(id) => L.elem(id, availableIds)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(fold => fold |> isNoRequiredOrActiveSelection >>~ isValidId);
|
||||
| PropertyFocus =>
|
||||
let activePropertyKnowledges =
|
||||
maybeHeroEntry |> O.option([], getActiveSelectOptions1);
|
||||
|
||||
let hasActivePropertyKnowledge = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(_) as id => L.elem(id, activePropertyKnowledges)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ hasActivePropertyKnowledge,
|
||||
);
|
||||
| AdaptionZauber =>
|
||||
let minimumSkillRating = 10;
|
||||
|
||||
let hasSpellMinimumSr = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Spell(id) =>
|
||||
IM.lookup(id, hero.spells)
|
||||
|> O.option(false, (spell: ActivatableSkill.Dynamic.t) =>
|
||||
switch (spell.value) {
|
||||
| Active(value) => value >= minimumSkillRating
|
||||
| Inactive => false
|
||||
}
|
||||
)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
let isUnfamiliarSpell = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Spell(id) =>
|
||||
IM.lookup(id, staticData.spells)
|
||||
|> O.option(
|
||||
false,
|
||||
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
|
||||
hero.transferredUnfamiliarSpells,
|
||||
magicalTraditions,
|
||||
),
|
||||
)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ hasSpellMinimumSr
|
||||
>>~ isUnfamiliarSpell,
|
||||
);
|
||||
| SpellEnhancement as id
|
||||
| ChantEnhancement as id =>
|
||||
let getTargetHeroEntry =
|
||||
switch (id) {
|
||||
| SpellEnhancement => (id => IM.lookup(id, hero.spells))
|
||||
| ChantEnhancement => (id => IM.lookup(id, hero.liturgicalChants))
|
||||
| _ => (_ => None)
|
||||
};
|
||||
|
||||
let isNotUnfamiliar =
|
||||
Ley_Bool.notP(
|
||||
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
|
||||
hero.transferredUnfamiliarSpells,
|
||||
magicalTraditions,
|
||||
),
|
||||
);
|
||||
|
||||
let isValueValid = (maybeLevel, heroEntry: ActivatableSkill.Dynamic.t) =>
|
||||
switch (maybeLevel, heroEntry.value) {
|
||||
| (Some(level), Active(value)) => value >= level * 4 + 4
|
||||
| (None, Active(_))
|
||||
| (None, Inactive)
|
||||
| (Some(_), Inactive) => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ (
|
||||
(selectOption: SO.t) =>
|
||||
switch (
|
||||
selectOption.staticEntry,
|
||||
O.Infix.(
|
||||
selectOption.enhancementTarget >>= getTargetHeroEntry
|
||||
),
|
||||
) {
|
||||
| (Some(Spell(staticSpell)), Some(heroSpell)) =>
|
||||
isNotUnfamiliar(staticSpell)
|
||||
&& isValueValid(selectOption.enhancementLevel, heroSpell)
|
||||
| (Some(LiturgicalChant(_)), Some(heroChant)) =>
|
||||
isValueValid(selectOption.enhancementLevel, heroChant)
|
||||
| _ => false
|
||||
}
|
||||
),
|
||||
);
|
||||
| LanguageSpecializations =>
|
||||
// Pair: fst = sid, snd = current_level
|
||||
let availableLanguages =
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(Language))
|
||||
|> O.option([], (heroLanguage: Activatable_Dynamic.t) =>
|
||||
heroLanguage.active
|
||||
|> O.mapOption((active: Activatable_Dynamic.single) =>
|
||||
O.Infix.(
|
||||
active.level
|
||||
>>= (
|
||||
level =>
|
||||
switch (level, active.options |> O.listToOption) {
|
||||
| (3 | 4, Some(Preset(Generic(sid)))) =>
|
||||
Some((sid, level))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
|> mapOptionT((selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(sid) =>
|
||||
switch (L.lookup(sid, availableLanguages)) {
|
||||
| Some(4) => Some({...selectOption, apValue: Some(0)})
|
||||
| Some(3) => Some(selectOption)
|
||||
| Some(_)
|
||||
| None => None
|
||||
}
|
||||
| _ => None
|
||||
}
|
||||
),
|
||||
);
|
||||
| MadaschwesternStil =>
|
||||
Some(
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.disjoint(
|
||||
spell.traditions,
|
||||
Ley_IntSet.fromList([
|
||||
Id.MagicalTradition.toInt(General),
|
||||
Id.MagicalTradition.toInt(Witches),
|
||||
]),
|
||||
)
|
||||
),
|
||||
)
|
||||
| ScholarDesMagierkollegsZuHoningen =>
|
||||
let allowedTraditions =
|
||||
[Id.MagicalTradition.Druids, Elves, Witches]
|
||||
|> L.map(Id.MagicalTradition.toInt)
|
||||
|> IS.fromList;
|
||||
|
||||
let maybeTransferredSpellFromTradition =
|
||||
O.Infix.(
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(TraditionGuildMages))
|
||||
>>= (
|
||||
specialAbility =>
|
||||
specialAbility.active
|
||||
|> O.listToOption
|
||||
>>= (
|
||||
active =>
|
||||
(
|
||||
switch (active.options |> O.listToOption) {
|
||||
| Some(Preset(Spell(id))) => Some(id)
|
||||
| Some(_)
|
||||
| None => None
|
||||
}
|
||||
)
|
||||
>>= F.flip(IM.lookup, staticData.spells)
|
||||
<&> (spell => spell.traditions)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
maybeTransferredSpellFromTradition
|
||||
|> O.option(
|
||||
idT,
|
||||
transferredSpellFromTradition => {
|
||||
// Contains all allowed trads the first spell does not have
|
||||
let traditionDiff =
|
||||
IS.Infix.(allowedTraditions \/ transferredSpellFromTradition);
|
||||
|
||||
let hasTransferredSpellAllAllowedTraditions =
|
||||
IS.null(traditionDiff);
|
||||
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.notElem(
|
||||
Id.MagicalTradition.toInt(General),
|
||||
spell.traditions,
|
||||
)
|
||||
&& Ley_IntSet.any(
|
||||
F.flip(
|
||||
IS.elem,
|
||||
hasTransferredSpellAllAllowedTraditions
|
||||
? allowedTraditions : traditionDiff,
|
||||
),
|
||||
spell.traditions,
|
||||
)
|
||||
);
|
||||
},
|
||||
)
|
||||
|> O.return;
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
let getAvailableSelectOptions =
|
||||
(
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
) => {
|
||||
let allSelectOptions =
|
||||
Activatable_Accessors.selectOptions(staticEntry) |> SO.Map.elems;
|
||||
|
||||
getAvailableSelectOptionsTransducer(
|
||||
staticData,
|
||||
hero,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
)
|
||||
|> (
|
||||
fun
|
||||
| Some(transducer) =>
|
||||
switch (transduceList(transducer, allSelectOptions)) {
|
||||
| [] => L.null(allSelectOptions) ? Some([]) : None
|
||||
| xs => Some(xs)
|
||||
}
|
||||
| None => Some(allSelectOptions)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* `getAvailableSelectOptions static hero magicalTraditions staticEntry
|
||||
* heroEntry` returns a list of possible select options for an inactive
|
||||
* activatable entry. Note: If `None` is returned, no select option is valid and
|
||||
* thus the entry cannot be added at all. This is different to a returned empty
|
||||
* list, which can happen if the entry just has no select options at all.
|
||||
*/
|
||||
let getAvailableSelectOptions:
|
||||
(
|
||||
Static.t,
|
||||
Hero.t,
|
||||
list(Tradition.Magical.fullTradition),
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
option(list(SelectOption.t));
|
||||
@@ -0,0 +1,245 @@
|
||||
module F = Ley_Function;
|
||||
module I = Ley_Int;
|
||||
module IM = Ley_IntMap;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
let getActivePactGiftsCount = specialAbilityPairs =>
|
||||
specialAbilityPairs
|
||||
|> EntryGroups.SpecialAbility.getFromGroup(Paktgeschenke)
|
||||
|> IM.foldr(
|
||||
fun
|
||||
| (
|
||||
{SpecialAbility.Static.prerequisites, apValue, levels, _},
|
||||
Some(({active, _}: Activatable_Dynamic.t) as heroEntry),
|
||||
)
|
||||
when Activatable_Accessors.isActive(heroEntry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (prerequisites, apValue, levels, active) {
|
||||
| (
|
||||
// It must have different prerequisites for each level
|
||||
ByLevel(_),
|
||||
// It must have different cost per level
|
||||
Some(PerLevel(_)),
|
||||
// It must have levels
|
||||
Some(_),
|
||||
// The current level must be set
|
||||
[{level: Some(level), _}, ..._],
|
||||
) =>
|
||||
// Then each level counts as one activated pact present
|
||||
(+)(level)
|
||||
| _ => (+)(1)
|
||||
}
|
||||
)
|
||||
| _ => F.id,
|
||||
0,
|
||||
);
|
||||
|
||||
let isSpecialAbilitySpecificAdditionEnabled =
|
||||
(
|
||||
~rules: Hero.Rules.t,
|
||||
~maybePact,
|
||||
{
|
||||
combatStyleCombination,
|
||||
armedCombatStylesCount,
|
||||
unarmedCombatStylesCount,
|
||||
magicalStyleCombination,
|
||||
magicalStylesCount,
|
||||
isBlessedStyleActive,
|
||||
dunklesAbbild,
|
||||
activePactGiftsCount,
|
||||
matchingLanguagesScripts,
|
||||
_,
|
||||
},
|
||||
specialAbility: SpecialAbility.Static.t,
|
||||
) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(specialAbility.id), Group.fromInt(specialAbility.gr)) {
|
||||
| (_, CombatStylesArmed as gr | CombatStylesUnarmed as gr) =>
|
||||
let equalTypeStylesActive =
|
||||
switch (gr) {
|
||||
| CombatStylesArmed => armedCombatStylesCount
|
||||
| CombatStylesUnarmed => unarmedCombatStylesCount
|
||||
| _ => 0
|
||||
};
|
||||
|
||||
// Combination-SA is active, which allows 3 styles to be active, but only a
|
||||
// maximum of 2 from one type (armed/unarmed).
|
||||
if (Activatable_Accessors.isActiveM(combatStyleCombination)) {
|
||||
let totalActive = armedCombatStylesCount + unarmedCombatStylesCount;
|
||||
|
||||
totalActive < 3 || equalTypeStylesActive < 2;
|
||||
} else {
|
||||
// Otherwise, only one of each type can be active.
|
||||
equalTypeStylesActive === 0;
|
||||
};
|
||||
|
||||
| (_, MagicalStyles) =>
|
||||
magicalStylesCount
|
||||
< (Activatable_Accessors.isActiveM(magicalStyleCombination) ? 2 : 1)
|
||||
|
||||
| (_, BlessedStyles) => !isBlessedStyleActive
|
||||
|
||||
| (CombatStyleCombination, _) =>
|
||||
armedCombatStylesCount + unarmedCombatStylesCount > 0
|
||||
|
||||
| (MagicStyleCombination, _) => magicalStylesCount > 0
|
||||
|
||||
| (DunklesAbbildDerBuendnisgabe, _) => activePactGiftsCount === 0
|
||||
|
||||
| (WegDerSchreiberin, _) =>
|
||||
L.length(matchingLanguagesScripts.languagesWithMatchingScripts) >= 1
|
||||
&& L.length(matchingLanguagesScripts.scriptsWithMatchingLanguages) >= 1
|
||||
|
||||
| (_, Paktgeschenke) =>
|
||||
switch (maybePact) {
|
||||
| Some((pact: Pact.Dynamic.t)) =>
|
||||
switch (Id.Pact.fromInt(pact.category)) {
|
||||
| Faery =>
|
||||
!Activatable_Accessors.isActiveM(dunklesAbbild)
|
||||
&& pact.level > activePactGiftsCount
|
||||
| Demon =>
|
||||
// Minor Pact
|
||||
pact.level <= 0
|
||||
// Minor Pact only allows up to 3 pact gifts
|
||||
? activePactGiftsCount < 3
|
||||
// "Normal" Pact allows up to CoD + 7 pact gifts
|
||||
: pact.level + 7 > activePactGiftsCount
|
||||
| Other(_) => false
|
||||
}
|
||||
| None => false
|
||||
}
|
||||
|
||||
| (LanguageSpecializations, _) =>
|
||||
IM.member(
|
||||
Id.OptionalRule.toInt(LanguageSpecialization),
|
||||
rules.activeOptionalRules,
|
||||
)
|
||||
|
||||
| (_, Vampirismus | Lykanthropie) =>
|
||||
// TODO: add option to activate vampire or lycanthropy and activate this
|
||||
// SAs based on that option
|
||||
false
|
||||
|
||||
| _ => true
|
||||
}
|
||||
);
|
||||
|
||||
let isEntrySpecificAdditionEnabled =
|
||||
(cache, staticData, hero: Hero.t, staticEntry) =>
|
||||
(
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
isSpecialAbilitySpecificAdditionEnabled(
|
||||
~rules=hero.rules,
|
||||
~maybePact=hero.pact,
|
||||
cache,
|
||||
staticSpecialAbility,
|
||||
)
|
||||
}
|
||||
)
|
||||
&& Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
|
||||
Prerequisites.Activatable.getFlatFirstPrerequisites(staticEntry),
|
||||
);
|
||||
|
||||
let hasNoGenerallyRestrictingDependency =
|
||||
fun
|
||||
| Some({Activatable_Dynamic.dependencies, _}) =>
|
||||
L.all(
|
||||
[@warning "-4"]
|
||||
(
|
||||
fun
|
||||
| {
|
||||
Activatable_Dynamic.target: One(_),
|
||||
options: [],
|
||||
level: None,
|
||||
active: false,
|
||||
_,
|
||||
} =>
|
||||
false
|
||||
| _ => true
|
||||
),
|
||||
dependencies,
|
||||
)
|
||||
| _ => true;
|
||||
|
||||
let hasNotReachedMaximumEntries = (maxLevel, maybeHeroEntry) =>
|
||||
switch (maxLevel, maybeHeroEntry) {
|
||||
// Impossible maximum level, thus no addition possible
|
||||
| (Some(0), _) => false
|
||||
| (Some(max), Some(({active, _}: Activatable_Dynamic.t))) =>
|
||||
max > L.length(active)
|
||||
| (Some(_), None)
|
||||
| (None, _) => true
|
||||
};
|
||||
|
||||
let isValidExtendedSpecialAbility =
|
||||
({validExtendedSpecialAbilities, _}, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.Group.(
|
||||
switch (fromInt(staticSpecialAbility.gr)) {
|
||||
| CombatExtended
|
||||
| MagicalExtended
|
||||
| KarmaExtended
|
||||
| SkillExtended =>
|
||||
L.elem(staticSpecialAbility.id, validExtendedSpecialAbilities)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let appliesToMagicalActionsIfRequired =
|
||||
({requiredApplyToMagicalActions, _}, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
!requiredApplyToMagicalActions
|
||||
|| !staticAdvantage.isExclusiveToArcaneSpellworks
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
!requiredApplyToMagicalActions
|
||||
|| !staticDisadvantage.isExclusiveToArcaneSpellworks
|
||||
| SpecialAbility(_) => true
|
||||
};
|
||||
|
||||
let isAdditionValid =
|
||||
(cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry) =>
|
||||
isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry)
|
||||
&& hasNoGenerallyRestrictingDependency(maybeHeroEntry)
|
||||
&& hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry)
|
||||
&& isValidExtendedSpecialAbility(cache, staticEntry)
|
||||
&& appliesToMagicalActionsIfRequired(cache, staticEntry);
|
||||
|
||||
let getMaxLevel = (staticData, hero, staticEntry, maybeHeroEntry) =>
|
||||
staticEntry
|
||||
|> Prerequisites.Activatable.getLevelPrerequisites
|
||||
|> Dependencies.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
|
||||
O.option(
|
||||
[],
|
||||
({Activatable_Dynamic.dependencies, _}) => dependencies,
|
||||
maybeHeroEntry,
|
||||
),
|
||||
)
|
||||
|> (
|
||||
computedMax =>
|
||||
switch (computedMax, Activatable_Accessors.max(staticEntry)) {
|
||||
| (Some(max1), Some(max2)) => Some(I.min(max1, max2))
|
||||
| (Some(max), None)
|
||||
| (None, Some(max)) => Some(max)
|
||||
| (None, None) => None
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Validate addition of inactive entries
|
||||
*
|
||||
* This module provides functions to determine if an (inactive) entry is allowed
|
||||
* to be bought/added.
|
||||
*/
|
||||
|
||||
/**
|
||||
* `getActivePactGiftsCount pairs` returns the amount of active pact gifts based
|
||||
* on the given set of static/hero special ability entry pairs.
|
||||
*/
|
||||
let getActivePactGiftsCount:
|
||||
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `isAdditionValid cache staticData hero maybeMaxLevel staticEntry
|
||||
* maybeHeroEntry` checks whether an activatable entry can be added/bought. The
|
||||
* `cache` contains computed values that are used for multiple entries so they
|
||||
* can be reused and thus improve performance for long lists of entries.
|
||||
*/
|
||||
let isAdditionValid:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
option(int),
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
bool;
|
||||
|
||||
/**
|
||||
* `getMaxLevel staticData staticEntry maybeHeroEntry` returns the maximum level based on the level prerequisites (excluding first level prerequisites) where the key is the level and the value the prerequisites that need to be met for the respective level as well as based on the registered `dependencies` of the entry.
|
||||
*
|
||||
* The return value is based on prerequisites *and* entry dependencies. To get
|
||||
* the max level only based on prerequisites, use
|
||||
* `Prerequisites.Validation.getMaxLevel`.
|
||||
*/
|
||||
let getMaxLevel:
|
||||
(Static.t, Hero.t, Activatable.t, option(Activatable_Dynamic.t)) =>
|
||||
option(int);
|
||||
@@ -0,0 +1,58 @@
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
let getLevel = (maybeEntry: option(Activatable_Dynamic.t)) =>
|
||||
O.Infix.(
|
||||
maybeEntry
|
||||
>>= (entry => entry.active |> O.listToOption)
|
||||
>>= (active => active.level)
|
||||
|> O.sum
|
||||
);
|
||||
|
||||
/**
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value.
|
||||
*/
|
||||
let modifyByLevel = (value, inc, dec) =>
|
||||
value + getLevel(inc) - getLevel(dec);
|
||||
|
||||
/**
|
||||
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
|
||||
* function always returns `0`.
|
||||
*/
|
||||
let modifyByLevelM = (value, inc, dec) =>
|
||||
O.option(0, value => modifyByLevel(value, inc, dec), value);
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
|
||||
* the entry `inc`, that should increase the base, is active, it adds `1` to the
|
||||
* base. If the entry `dec`, that should decrease the base, is active, it
|
||||
* subtracts `1` from the base. If the passed base is `Nothing`, this function
|
||||
* always returns `0`.
|
||||
*/
|
||||
let getModifierByIsActive = (value, inc, dec) =>
|
||||
O.option(
|
||||
0,
|
||||
value =>
|
||||
Activatable_Accessors.isActiveM(inc)
|
||||
? value + 1 : Activatable_Accessors.isActiveM(dec) ? value - 1 : value,
|
||||
value,
|
||||
);
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
|
||||
* summing all active entries that should increase the base (`incs`) and all
|
||||
* active entries that should decrease the base (`decs`).
|
||||
*/
|
||||
let getModifierByIsActives = (value, incs, decs) =>
|
||||
O.option(
|
||||
0,
|
||||
value =>
|
||||
value
|
||||
+ L.countBy(Activatable_Accessors.isActiveM, incs)
|
||||
- L.countBy(Activatable_Accessors.isActiveM, decs),
|
||||
value,
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value.
|
||||
*/
|
||||
let modifyByLevel:
|
||||
(int, option(Activatable_Dynamic.t), option(Activatable_Dynamic.t)) => int;
|
||||
|
||||
/**
|
||||
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
|
||||
* function always returns `0`.
|
||||
*/
|
||||
let modifyByLevelM:
|
||||
(
|
||||
option(int),
|
||||
option(Activatable_Dynamic.t),
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
|
||||
* the entry `inc`, that should increase the base, is active, it adds `1` to the
|
||||
* base. If the entry `dec`, that should decrease the base, is active, it
|
||||
* subtracts `1` from the base. If the passed base is `Nothing`, this function
|
||||
* always returns `0`.
|
||||
*/
|
||||
let getModifierByIsActive:
|
||||
(
|
||||
option(int),
|
||||
option(Activatable_Dynamic.t),
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
|
||||
* summing all active entries that should increase the base (`incs`) and all
|
||||
* active entries that should decrease the base (`decs`).
|
||||
*/
|
||||
let getModifierByIsActives:
|
||||
(
|
||||
option(int),
|
||||
list(option(Activatable_Dynamic.t)),
|
||||
list(option(Activatable_Dynamic.t))
|
||||
) =>
|
||||
int;
|
||||
@@ -0,0 +1,130 @@
|
||||
open SelectOption;
|
||||
open Ley_Option.Infix;
|
||||
open Activatable_Convert;
|
||||
open Activatable_Accessors;
|
||||
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
module SOM = SelectOption.Map;
|
||||
module F = Ley_Function;
|
||||
|
||||
let getSelectOption = (x, id) => Map.lookup(id, selectOptions(x));
|
||||
|
||||
/**
|
||||
* Get a selection option's name with the given id from given wiki entry.
|
||||
* Returns `None` if not found.
|
||||
*/
|
||||
let getSelectOptionName = (x, id) =>
|
||||
id |> getSelectOption(x) <&> (y => y.name);
|
||||
|
||||
/**
|
||||
* Get a selection option's cost with the given id from given wiki entry.
|
||||
* Returns `None` if not found.
|
||||
*/
|
||||
let getSelectOptionCost = (x, id) =>
|
||||
id |> getSelectOption(x) >>= (y => y.apValue);
|
||||
|
||||
/**
|
||||
* Get all option IDs from the given entry at the passed index.
|
||||
*/
|
||||
let getActiveOptions = (index, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
Ley_List.Safe.atMay(y.options, index)
|
||||
);
|
||||
|
||||
let mapActiveOptions = (f, index, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
Ley_List.Safe.atMay(y.options, index) >>= f
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all first option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions1 = (x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
y.options |> O.listToOption
|
||||
);
|
||||
|
||||
let mapActiveOptions1 = (f, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
y.options |> O.listToOption >>= f
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all first select option IDs from the given entry.
|
||||
*/
|
||||
let getActiveSelectOptions1 =
|
||||
mapActiveOptions1(Activatable_Convert.activatableOptionToSelectOptionId);
|
||||
|
||||
/**
|
||||
* Get all second option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions2 = getActiveOptions(1);
|
||||
|
||||
/**
|
||||
* Get all second option ids from the given entry, sorted by their first option
|
||||
* id in a map.
|
||||
*/
|
||||
let getActiveOptions2Map = (x: Activatable_Dynamic.t) =>
|
||||
L.foldr(
|
||||
(current: Activatable_Dynamic.single, mp) =>
|
||||
current.options
|
||||
|> O.listToOption
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
|> O.liftM2(
|
||||
(secondOption, option) =>
|
||||
SOM.alter(
|
||||
maybeSecondOptions =>
|
||||
maybeSecondOptions
|
||||
|> O.fromOption([])
|
||||
|> L.cons(secondOption)
|
||||
|> (xs => Some(xs)),
|
||||
option,
|
||||
mp,
|
||||
),
|
||||
current.options |> F.flip(L.Safe.atMay, 1),
|
||||
)
|
||||
|> O.fromOption(mp),
|
||||
SOM.empty,
|
||||
x.active,
|
||||
);
|
||||
|
||||
let getOption = (index, heroEntry) =>
|
||||
Ley_List.Safe.atMay(heroEntry.options, index);
|
||||
|
||||
let getOption1 = heroEntry => heroEntry.options |> Ley_Option.listToOption;
|
||||
|
||||
let getOption2 = getOption(1);
|
||||
|
||||
let getOption3 = getOption(2);
|
||||
|
||||
let getCustomInput = (option: Id.Activatable.Option.t) =>
|
||||
switch (option) {
|
||||
| CustomInput(x) => Some(x)
|
||||
| Preset(_) => None
|
||||
};
|
||||
|
||||
let getGenericId = (option: Id.Activatable.Option.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (option) {
|
||||
| Preset(Generic(x)) => Some(x)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
let lookupMap = (k, mp, f) => f <$> Ley_IntMap.lookup(k, mp);
|
||||
|
||||
let getSkillFromOption =
|
||||
(staticData: Static.t, option: Id.Activatable.Option.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (option) {
|
||||
| Preset(Skill(id)) => Ley_IntMap.lookup(id, staticData.skills)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Get specific Select Options.
|
||||
*
|
||||
* This module provides some utility functions for working with static and
|
||||
* active Select Options.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* Get a select option with the given id from given static entry. Returns
|
||||
* `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOption:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(SelectOption.t);
|
||||
|
||||
/**
|
||||
* Get a select option's name with the given id from given static entry.
|
||||
* Returns `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOptionName:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(string);
|
||||
|
||||
/**
|
||||
* Get a option's cost with the given id from given static entry. Returns
|
||||
* `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOptionCost:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(int);
|
||||
|
||||
/**
|
||||
* Get all first option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions1: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
|
||||
|
||||
/**
|
||||
* `mapActiveOptions1 f entry` takes all first option ids from the given entry
|
||||
* and returns all values where the passed function, applied to each id,
|
||||
* returned a `Some`, which is then unwrapped.
|
||||
*/
|
||||
let mapActiveOptions1:
|
||||
(Id.Activatable.Option.t => option('a), Activatable_Dynamic.t) => list('a);
|
||||
|
||||
/**
|
||||
* Get all first select option IDs from the given entry.
|
||||
*/
|
||||
let getActiveSelectOptions1:
|
||||
Activatable_Dynamic.t => list(Id.Activatable.SelectOption.t);
|
||||
|
||||
/**
|
||||
* Get all second option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions2: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
|
||||
|
||||
/**
|
||||
* Get all second option ids from the given entry, sorted by their first option
|
||||
* id in a map.
|
||||
*/
|
||||
let getActiveOptions2Map:
|
||||
Activatable_Dynamic.t => SelectOption.Map.t(list(Id.Activatable.Option.t));
|
||||
|
||||
let getOption: (int, singleWithId) => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption1: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption2: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption3: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getCustomInput: Id.Activatable.Option.t => option(string);
|
||||
|
||||
let getGenericId: Id.Activatable.Option.t => option(int);
|
||||
|
||||
let lookupMap: (Ley_IntMap.key, Ley_IntMap.t('b), 'b => 'a) => option('a);
|
||||
|
||||
let getSkillFromOption:
|
||||
(Static.t, Id.Activatable.Option.t) => option(Skill.Static.t);
|
||||
@@ -0,0 +1,464 @@
|
||||
module Id = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Name = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module NameInLibrary = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Levels = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Maximum = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Input = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Rules = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Effect = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module CombatSpecialAbilityType = struct
|
||||
type t = Passive | BaseManeuver | SpecialManeuver
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
string
|
||||
|> map (function
|
||||
| "Passive" -> Passive
|
||||
| "BaseManeuver" -> BaseManeuver
|
||||
| "SpecialManeuver" -> SpecialManeuver
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError ("Unknown CombatSpecialAbilityType: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module Penalty = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module AeCost = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Volume = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module BindingCost = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Property = struct
|
||||
type t = DependingOnProperty | Single of int
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Passive" -> fun _ -> DependingOnProperty
|
||||
| "Single" -> field "value" int |> map (fun x -> Single x)
|
||||
| str -> raise (DecodeError ("Unknown Property: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module Aspect = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module AdvancedSpecialAbilities = struct
|
||||
type slot =
|
||||
| Single of {
|
||||
id : int;
|
||||
option :
|
||||
OptolithClient.Id.Activatable.SelectOption.t NonEmptyList.t option;
|
||||
(** Only one specific select option or one of a set of select
|
||||
options is allowed for the referenced advanced special
|
||||
ability. *)
|
||||
}
|
||||
| OneOf of int list
|
||||
|
||||
type t = slot * slot * slot
|
||||
|
||||
module Decode = struct
|
||||
let slot =
|
||||
Json_Decode_Strict.(
|
||||
oneOf
|
||||
[
|
||||
list int |> map (fun xs -> OneOf xs);
|
||||
(fun json ->
|
||||
Single
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
option =
|
||||
json
|
||||
|> optionalField "option"
|
||||
(NonEmptyList.Decode.one_or_many
|
||||
OptolithClient.Id.Activatable.SelectOption.Decode.t);
|
||||
});
|
||||
])
|
||||
|
||||
let t = Json.Decode.tuple3 slot slot slot
|
||||
end
|
||||
end
|
||||
|
||||
module ApplicableCombatTechniques = struct
|
||||
type generalRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of OptolithClient.Id.CombatTechnique.t list
|
||||
|
||||
type meleeRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| HasParry
|
||||
| OneHanded
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type rangedRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type specificRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| Level of int (** Only for a certain level of the special ability *)
|
||||
| Weapons of int list (** Only certain weapons *)
|
||||
|
||||
type specific = {
|
||||
id : OptolithClient.Id.CombatTechnique.t;
|
||||
restrictions : specificRestriction list;
|
||||
}
|
||||
|
||||
type t =
|
||||
| All of generalRestriction list
|
||||
| AllMelee of meleeRestriction list
|
||||
| AllRanged of rangedRestriction list
|
||||
| Specific of specific list
|
||||
| DependingOnCombatStyle
|
||||
| NotApplicable (** This is represented by a dash. *)
|
||||
|
||||
module Decode = struct
|
||||
let generalRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : generalRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : generalRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : generalRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : generalRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list OptolithClient.Id.CombatTechnique.Decode.t)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : generalRestriction))
|
||||
| str ->
|
||||
raise (DecodeError ("Unknown general restriction: " ^ str))))
|
||||
|
||||
let meleeRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : meleeRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : meleeRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : meleeRestriction)
|
||||
| "HasParry" -> fun _ -> HasParry
|
||||
| "OneHanded" -> fun _ -> OneHanded
|
||||
| "Race" ->
|
||||
field "value" int |> map (fun x -> (Race x : meleeRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : meleeRestriction))
|
||||
| str -> raise (DecodeError ("Unknown melee restriction: " ^ str))))
|
||||
|
||||
let rangedRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : rangedRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : rangedRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : rangedRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : rangedRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : rangedRestriction))
|
||||
| str -> raise (DecodeError ("Unknown ranged restriction: " ^ str))))
|
||||
|
||||
let specificRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : specificRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : specificRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : specificRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : specificRestriction))
|
||||
| "Level" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Level x : specificRestriction))
|
||||
| "Weapons" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (Weapons xs : specificRestriction))
|
||||
| str ->
|
||||
raise (DecodeError ("Unknown specific restriction: " ^ str))))
|
||||
|
||||
let specific json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" OptolithClient.Id.CombatTechnique.Decode.t;
|
||||
restrictions =
|
||||
json
|
||||
|> optionalField "restrictions" (list specificRestriction)
|
||||
|> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "None" -> fun _ -> NotApplicable
|
||||
| "DependingOnCombatStyle" -> fun _ -> DependingOnCombatStyle
|
||||
| "All" ->
|
||||
field "value" (list generalRestriction)
|
||||
|> map (fun xs -> All xs)
|
||||
| "AllMelee" ->
|
||||
field "value" (list meleeRestriction)
|
||||
|> map (fun xs -> AllMelee xs)
|
||||
| "AllRanged" ->
|
||||
field "value" (list rangedRestriction)
|
||||
|> map (fun xs -> AllRanged xs)
|
||||
| "Specific" ->
|
||||
field "value" (list specific) |> map (fun xs -> Specific xs)
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError
|
||||
("Unknown applicable combat techniques option: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesReplacement = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesStart = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesEnd = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module ApValue = struct
|
||||
type t = Flat of int | PerLevel of int list | Option
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Flat" -> field "value" int |> map (fun x -> Flat x)
|
||||
| "PerLevel" ->
|
||||
field "value" (list int) |> map (fun xs -> PerLevel xs)
|
||||
| "Option" -> fun _ -> Option
|
||||
| str -> raise (DecodeError ("Unknown AP value: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueReplacement = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueAppend = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Decode = struct
|
||||
(** Types and functions that must be provided for an entity in order to derive
|
||||
the final decoders. *)
|
||||
module type Entity = sig
|
||||
type t
|
||||
|
||||
module Translation : Json_Decode_TranslationMap.EntityTranslation
|
||||
|
||||
type multilingual
|
||||
|
||||
val multilingual :
|
||||
(Js.Json.t -> Translation.t Json_Decode_TranslationMap.partial) ->
|
||||
Js.Json.t ->
|
||||
multilingual
|
||||
|
||||
val make :
|
||||
resolveSelectOptions:
|
||||
(src:PublicationRef.t list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
SelectOption.Decode.multilingual ->
|
||||
SelectOption.map) ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
Translation.t ->
|
||||
(int * t) option
|
||||
|
||||
(** Accessors are needed to provide the final translation to [make] as well as
|
||||
to get the id of the entry for [toAssoc]. *)
|
||||
module Accessors : sig
|
||||
val translations :
|
||||
multilingual -> Translation.t Json_Decode_TranslationMap.partial
|
||||
end
|
||||
end
|
||||
|
||||
type 'a decodeAssoc =
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:OptolithClient.Property.t Ley_IntMap.t ->
|
||||
aspects:OptolithClient.Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
Locale.Order.t ->
|
||||
Js.Json.t ->
|
||||
(int * 'a) option
|
||||
|
||||
(** Functor to derive the final decoders from the types and functions for the
|
||||
entity. *)
|
||||
module Make (E : Entity) = struct
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (E.Translation)
|
||||
|
||||
let multilingual = E.multilingual TranslationMap.t
|
||||
|
||||
let resolveTranslations ~resolveSelectOptions langs x =
|
||||
Ley_Option.Infix.(
|
||||
x |> E.Accessors.translations
|
||||
|> TranslationMap.getFromLanguageOrder langs
|
||||
>>= E.make ~resolveSelectOptions langs x)
|
||||
|
||||
let assoc ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals langs json =
|
||||
json |> multilingual
|
||||
|> resolveTranslations
|
||||
~resolveSelectOptions:
|
||||
(SelectOption.Decode.resolve ~blessings ~cantrips ~trade_secrets
|
||||
~languages ~scripts ~animal_shapes ~animal_shape_sizes
|
||||
~arcane_bard_traditions ~arcane_dancer_traditions ~elements
|
||||
~properties ~aspects ~diseases ~poisons ~melee_combat_techniques
|
||||
~ranged_combat_techniques ~liturgical_chants ~ceremonies ~skills
|
||||
~spells ~rituals)
|
||||
langs
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,335 @@
|
||||
module Id : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Name : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module NameInLibrary : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Levels : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Maximum : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Input : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Rules : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Effect : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module CombatSpecialAbilityType : sig
|
||||
type t = Passive | BaseManeuver | SpecialManeuver
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Penalty : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module AeCost : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Volume : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module BindingCost : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Property : sig
|
||||
type t = DependingOnProperty | Single of int
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Aspect : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module AdvancedSpecialAbilities : sig
|
||||
type slot =
|
||||
| Single of {
|
||||
id : int;
|
||||
option :
|
||||
OptolithClient.Id.Activatable.SelectOption.t NonEmptyList.t option;
|
||||
(** Only one specific select option or one of a set of select
|
||||
options is allowed for the referenced advanced special
|
||||
ability. *)
|
||||
}
|
||||
| OneOf of int list
|
||||
|
||||
type t = slot * slot * slot
|
||||
|
||||
module Decode : sig
|
||||
val slot : slot Json.Decode.decoder
|
||||
|
||||
val t : (slot * slot * slot) Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApplicableCombatTechniques : sig
|
||||
type generalRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of OptolithClient.Id.CombatTechnique.t list
|
||||
|
||||
type meleeRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| HasParry
|
||||
| OneHanded
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type rangedRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type specificRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| Level of int (** Only for a certain level of the special ability *)
|
||||
| Weapons of int list (** Only certain weapons *)
|
||||
|
||||
type specific = {
|
||||
id : OptolithClient.Id.CombatTechnique.t;
|
||||
restrictions : specificRestriction list;
|
||||
}
|
||||
|
||||
type t =
|
||||
| All of generalRestriction list
|
||||
| AllMelee of meleeRestriction list
|
||||
| AllRanged of rangedRestriction list
|
||||
| Specific of specific list
|
||||
| DependingOnCombatStyle
|
||||
| NotApplicable (** This is represented by a dash. *)
|
||||
|
||||
module Decode : sig
|
||||
val generalRestriction : generalRestriction Json.Decode.decoder
|
||||
|
||||
val meleeRestriction : meleeRestriction Json.Decode.decoder
|
||||
|
||||
val rangedRestriction : rangedRestriction Json.Decode.decoder
|
||||
|
||||
val specificRestriction : specificRestriction Json.Decode.decoder
|
||||
|
||||
val specific : specific Json.Decode.decoder
|
||||
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesReplacement : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesStart : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesEnd : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValue : sig
|
||||
type t = Flat of int | PerLevel of int list | Option
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueReplacement : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueAppend : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Decode : sig
|
||||
(** Required types, accessors and decoders to compose the final decoder *)
|
||||
module type Entity = sig
|
||||
type t
|
||||
(** The entity record type. *)
|
||||
|
||||
module Translation : Json_Decode_TranslationMap.EntityTranslation
|
||||
|
||||
type multilingual
|
||||
(** The record type with language-independent types. It represents the
|
||||
equivalent of a YAML file's content. Any adjustments to improve the
|
||||
usability in code should be handled in [make]. *)
|
||||
|
||||
val multilingual :
|
||||
(Js.Json.t -> Translation.t Json_Decode_TranslationMap.partial) ->
|
||||
Js.Json.t ->
|
||||
multilingual
|
||||
(** [t decodeTranslations json] decodes the passed JSON as the record with
|
||||
language-independent values. It has to decode the language-dependent values
|
||||
using [decodeTranslations].
|
||||
|
||||
@raise [DecodeError] if one decoder did not succeed. *)
|
||||
|
||||
val make :
|
||||
resolveSelectOptions:
|
||||
(src:PublicationRef.t list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
SelectOption.Decode.multilingual ->
|
||||
SelectOption.map) ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
Translation.t ->
|
||||
(int * t) option
|
||||
(** [make langs multilingual translation] merges the multilingual record with
|
||||
the record of the most wanted language available and should handle any
|
||||
type conversions that differ from the JSON schema for the YAML files. If
|
||||
inputs needs to be verified logically, this can be done here and [None]
|
||||
can be returned to exclude invalid entries. [langs] can be used to resolve
|
||||
translations of nested entities. *)
|
||||
|
||||
(** Accessors for record properties needed because of abstract types. *)
|
||||
module Accessors : sig
|
||||
val translations :
|
||||
multilingual -> Translation.t Json_Decode_TranslationMap.partial
|
||||
(** [translations x] returns the [translations] property from the
|
||||
[multilingual] record. *)
|
||||
end
|
||||
end
|
||||
|
||||
type 'a decodeAssoc =
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:OptolithClient.Property.t Ley_IntMap.t ->
|
||||
aspects:OptolithClient.Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
Locale.Order.t ->
|
||||
Js.Json.t ->
|
||||
(int * 'a) option
|
||||
|
||||
(** Create unified decoders using the passed entity module. *)
|
||||
module Make (E : Entity) : sig
|
||||
val assoc : E.t decodeAssoc
|
||||
(** [assoc localeOrder json] decodes the passed JSON into a value of type [t]
|
||||
of the passed module. It returns [None] if no matching translation could
|
||||
be found. It returns a pair to be used for inserting into a map.
|
||||
|
||||
@raise [DecodeError] if one decoder did not succeed. *)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
range = json |> optionalField "range" string;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
noMaxAPInfluence : bool option;
|
||||
isExclusiveToArcaneSpellworks : bool option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
gr : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
noMaxAPInfluence = json |> optionalField "noMaxAPInfluence" bool;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
json |> optionalField "isExclusiveToArcaneSpellworks" bool;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
gr = json |> field "gr" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
noMaxAPInfluence =
|
||||
multilingual.noMaxAPInfluence |> Ley_Option.fromOption false;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
multilingual.isExclusiveToArcaneSpellworks
|
||||
|> Ley_Option.fromOption false;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
range = translation.range;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
gr = multilingual.gr;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,31 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,64 @@
|
||||
type t = { id : int; name : string; path : int; size : int }
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = { name : string }
|
||||
|
||||
let t json = Json.Decode.{ name = json |> field "name" string }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
path : int;
|
||||
size : int;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
path = json |> field "path" int;
|
||||
size = json |> field "size" int;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make _ (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
path = multilingual.path;
|
||||
size = multilingual.size;
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
|
||||
module Size = struct
|
||||
type t = { id : int; volume : int; apValue : int }
|
||||
|
||||
module Decode = struct
|
||||
let t json =
|
||||
Some
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
volume = json |> field "volume" int;
|
||||
apValue = json |> field "apValue" int;
|
||||
}
|
||||
|
||||
let toAssoc (x : t) = (x.id, x)
|
||||
|
||||
let assoc (_ : Locale.Order.t) json =
|
||||
Ley_Option.Infix.(json |> t <&> toAssoc)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.t;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = { name : string }
|
||||
|
||||
let t json = Json_Decode_Strict.{ name = json |> field "name" string }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.Decode.multilingual;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.ArcaneTradition.Decode.multilingual;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.ArcaneTradition.Decode.resolveTranslations
|
||||
langs multilingual.prerequisites;
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,9 @@
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.t;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
effect = json |> field "effect" string;
|
||||
range = json |> field "range" string;
|
||||
duration = json |> field "duration" string;
|
||||
target = json |> field "target" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
effect = translation.effect;
|
||||
range = translation.range;
|
||||
duration = translation.duration;
|
||||
target = translation.target;
|
||||
src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src;
|
||||
errata = translation.errata |> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
property : int;
|
||||
traditions : Ley_IntSet.t;
|
||||
prerequisites : Prerequisite.Collection.Activatable.t;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
effect = json |> field "effect" string;
|
||||
range = json |> field "range" string;
|
||||
duration = json |> field "duration" string;
|
||||
target = json |> field "target" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
property : int;
|
||||
traditions : int list;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.Activatable.Decode.multilingual option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
property = json |> field "property" int;
|
||||
traditions = json |> field "traditions" (list int);
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.Activatable.Decode.multilingual;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
effect = translation.effect;
|
||||
range = translation.range;
|
||||
duration = translation.duration;
|
||||
target = translation.target;
|
||||
property = multilingual.property;
|
||||
traditions = multilingual.traditions |> Ley_IntSet.fromList;
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
|> Ley_Option.option []
|
||||
(Prerequisite.Collection.Activatable.Decode.resolveTranslations
|
||||
langs);
|
||||
src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src;
|
||||
errata = translation.errata |> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
property : int;
|
||||
traditions : Ley_IntSet.t;
|
||||
prerequisites : Prerequisite.Collection.Activatable.t;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
range = json |> optionalField "range" string;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
noMaxAPInfluence : bool option;
|
||||
isExclusiveToArcaneSpellworks : bool option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
gr : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
noMaxAPInfluence = json |> optionalField "noMaxAPInfluence" bool;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
json |> optionalField "isExclusiveToArcaneSpellworks" bool;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
gr = json |> field "gr" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
noMaxAPInfluence =
|
||||
multilingual.noMaxAPInfluence |> Ley_Option.fromOption false;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
multilingual.isExclusiveToArcaneSpellworks
|
||||
|> Ley_Option.fromOption false;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
range = translation.range;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
gr = multilingual.gr;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,31 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,970 @@
|
||||
type staticEntry =
|
||||
| Blessing of Blessing.Static.t
|
||||
| Cantrip of Cantrip.Static.t
|
||||
| TradeSecret of TradeSecret.t
|
||||
| Language of Language.t
|
||||
| Script of Script.t
|
||||
| AnimalShape of AnimalShape.t
|
||||
| SpellEnhancement of Enhancement.t
|
||||
| LiturgicalChantEnhancement of Enhancement.t
|
||||
| ArcaneBardTradition of ArcaneTradition.t
|
||||
| ArcaneDancerTradition of ArcaneTradition.t
|
||||
| Element of Element.t
|
||||
| Property of Property.t
|
||||
| Aspect of Aspect.t
|
||||
| Disease of Disease.t
|
||||
| Poison of Poison.t
|
||||
| MeleeCombatTechnique of CombatTechnique.Melee.Static.t
|
||||
| RangedCombatTechnique of CombatTechnique.Ranged.Static.t
|
||||
| LiturgicalChant of LiturgicalChant.Static.t
|
||||
| Ceremony of Ceremony.Static.t
|
||||
| Skill of Skill.Static.t
|
||||
| Spell of Spell.Static.t
|
||||
| Ritual of Ritual.Static.t
|
||||
|
||||
type t = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
name : string;
|
||||
apValue : int option;
|
||||
prerequisites : Prerequisite.Collection.General.t;
|
||||
description : string option;
|
||||
isSecret : bool option;
|
||||
languages : int list option;
|
||||
continent : int option;
|
||||
isExtinct : bool option;
|
||||
specializations : string list option;
|
||||
specializationInput : string option;
|
||||
animalGr : int option;
|
||||
animalLevel : int option;
|
||||
enhancementTarget : int option;
|
||||
enhancementLevel : int option;
|
||||
staticEntry : staticEntry option;
|
||||
applications : Skill.Static.Application.t list option;
|
||||
(** needed to be able to filter valid applications without altering the static entry *)
|
||||
src : PublicationRef.t list;
|
||||
errata : Erratum.t list;
|
||||
}
|
||||
|
||||
module Map = Ley_Map.Make (Id.Activatable.SelectOption)
|
||||
|
||||
type map = t Map.t
|
||||
|
||||
module Decode = struct
|
||||
module F = Ley_Function
|
||||
module L = Ley_List
|
||||
module O = Ley_Option
|
||||
module IM = Ley_IntMap
|
||||
|
||||
module Conversion = struct
|
||||
let make ~id ~name ?apValue ~staticEntry ~src ~errata () =
|
||||
{
|
||||
id;
|
||||
name;
|
||||
apValue;
|
||||
prerequisites = Plain [];
|
||||
description = None;
|
||||
isSecret = None;
|
||||
languages = None;
|
||||
continent = None;
|
||||
isExtinct = None;
|
||||
specializations = None;
|
||||
specializationInput = None;
|
||||
animalGr = None;
|
||||
animalLevel = None;
|
||||
enhancementTarget = None;
|
||||
enhancementLevel = None;
|
||||
staticEntry = Some staticEntry;
|
||||
applications = None;
|
||||
src;
|
||||
errata;
|
||||
}
|
||||
|
||||
let from_blessing (x : Blessing.Static.t) =
|
||||
make ~id:(Blessing x.id) ~name:x.name ~staticEntry:(Blessing x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_cantrip (x : Cantrip.Static.t) =
|
||||
make ~id:(Cantrip x.id) ~name:x.name ~staticEntry:(Cantrip x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_melee_combat_technique (x : CombatTechnique.Melee.Static.t) =
|
||||
make ~id:(MeleeCombatTechnique x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(MeleeCombatTechnique x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ranged_combat_technique (x : CombatTechnique.Ranged.Static.t) =
|
||||
make ~id:(RangedCombatTechnique x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(RangedCombatTechnique x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_liturgical_chant (x : LiturgicalChant.Static.t) =
|
||||
make ~id:(LiturgicalChant x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(LiturgicalChant x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ceremony (x : Ceremony.Static.t) =
|
||||
make ~id:(Ceremony x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Ceremony x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_skill (x : Skill.Static.t) =
|
||||
make ~id:(Skill x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Skill x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_spell (x : Spell.Static.t) =
|
||||
make ~id:(Spell x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Spell x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ritual (x : Ritual.Static.t) =
|
||||
make ~id:(Ritual x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Ritual x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_trade_secret (x : TradeSecret.t) =
|
||||
make ~id:(TradeSecret x.id) ~name:x.name ~staticEntry:(TradeSecret x)
|
||||
~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_language (x : Language.t) =
|
||||
make ~id:(Language x.id) ~name:x.name ~staticEntry:(Language x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_script (x : Script.t) =
|
||||
make ~id:(Script x.id) ~name:x.name ~apValue:x.apValue
|
||||
~staticEntry:(Script x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_animal_shape animal_shape_sizes src errata (x : AnimalShape.t) =
|
||||
make ~id:(AnimalShape x.id) ~name:x.name
|
||||
?apValue:
|
||||
O.Infix.(
|
||||
animal_shape_sizes |> Ley_IntMap.lookup x.size
|
||||
<&> fun { AnimalShape.Size.apValue; _ } -> apValue)
|
||||
~staticEntry:(AnimalShape x) ~src ~errata ()
|
||||
|
||||
let from_arcane_bard_tradition src errata (x : ArcaneTradition.t) =
|
||||
make ~id:(ArcaneBardTradition x.id) ~name:x.name
|
||||
~staticEntry:(ArcaneBardTradition x) ~src ~errata ()
|
||||
|
||||
let from_arcane_dancer_tradition src errata (x : ArcaneTradition.t) =
|
||||
make ~id:(ArcaneDancerTradition x.id) ~name:x.name
|
||||
~staticEntry:(ArcaneDancerTradition x) ~src ~errata ()
|
||||
|
||||
let from_disease (x : Disease.t) =
|
||||
make ~id:(Disease x.id) ~name:x.name ~apValue:x.level
|
||||
~staticEntry:(Disease x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_poison (x : Poison.t) =
|
||||
make ~id:(Poison x.id) ~name:x.name
|
||||
~apValue:(match x.level with QL -> 6 | Fixed level -> level)
|
||||
~staticEntry:(Poison x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_aspect src errata (x : Aspect.t) =
|
||||
make ~id:(Aspect x.id) ~name:x.name ~staticEntry:(Aspect x) ~src ~errata
|
||||
()
|
||||
|
||||
let from_element src errata (x : Element.t) =
|
||||
make ~id:(Element x.id) ~name:x.name ~staticEntry:(Element x) ~src ~errata
|
||||
()
|
||||
|
||||
let from_property src errata (x : Property.t) =
|
||||
make ~id:(Property x.id) ~name:x.name ~staticEntry:(Property x) ~src
|
||||
~errata ()
|
||||
end
|
||||
|
||||
module Derived = struct
|
||||
type select_option = t
|
||||
|
||||
type selectOptionPrerequisite = {
|
||||
target : Id.Activatable.t;
|
||||
active : bool;
|
||||
level : int option;
|
||||
}
|
||||
|
||||
let selectOptionPrerequisite json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
target = json |> field "target" Id.Activatable.Decode.t;
|
||||
active = json |> field "active" bool;
|
||||
level = json |> optionalField "level" int;
|
||||
}
|
||||
|
||||
type increasablePrerequisite =
|
||||
| Self of int
|
||||
| SelectOption of selectOptionPrerequisite
|
||||
|
||||
let increasablePrerequisite =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Self" -> field "value" int |> map (fun x -> Self x)
|
||||
| "SelectOption" ->
|
||||
field "value" selectOptionPrerequisite
|
||||
|> map (fun x -> SelectOption x)
|
||||
| str -> raise (DecodeError ("Unknown prerequisite type: " ^ str))))
|
||||
|
||||
type increasableApValue =
|
||||
| DerivedFromIC of int
|
||||
| Fixed of { list : int Ley_IntMap.t; default : int }
|
||||
|
||||
let increasableApValue =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "DerivedFromIC" ->
|
||||
field "value" int |> map (fun x -> DerivedFromIC x)
|
||||
| "Fixed" ->
|
||||
let element json =
|
||||
(json |> field "id" int, json |> field "apValue" int)
|
||||
in
|
||||
let fixed json =
|
||||
Fixed
|
||||
{
|
||||
list =
|
||||
json
|
||||
|> field "list" (list element)
|
||||
|> Ley_IntMap.fromList;
|
||||
default = json |> field "default" int;
|
||||
}
|
||||
in
|
||||
field "value" fixed
|
||||
| str -> raise (DecodeError ("Unknown AP value type: " ^ str))))
|
||||
|
||||
type specific = Include of int list | Exclude of int list
|
||||
|
||||
let specific json =
|
||||
Ley_Option.Infix.(
|
||||
Json_Decode_Strict.(
|
||||
let include_list =
|
||||
json |> optionalField "specific" (list int) <&> fun xs -> Include xs
|
||||
in
|
||||
let exclude_list =
|
||||
json |> optionalField "exclude" (list int) <&> fun xs -> Exclude xs
|
||||
in
|
||||
include_list <|> exclude_list))
|
||||
|
||||
type skillWithoutGroups = {
|
||||
specific : specific option;
|
||||
prerequisites : increasablePrerequisite list option;
|
||||
apValue : increasableApValue option;
|
||||
}
|
||||
|
||||
let skillWithoutGroups json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
specific = json |> specific;
|
||||
prerequisites =
|
||||
json |> optionalField "prerequisites" (list increasablePrerequisite);
|
||||
apValue = json |> optionalField "apValue" increasableApValue;
|
||||
}
|
||||
|
||||
type t =
|
||||
| Blessings
|
||||
| Cantrips
|
||||
| TradeSecrets
|
||||
| Scripts
|
||||
| AnimalShapes
|
||||
| ArcaneBardTraditions
|
||||
| ArcaneDancerTraditions
|
||||
| Elements of { specific : specific option }
|
||||
| Properties of { requireKnowledge : bool option }
|
||||
| Aspects of {
|
||||
requireKnowledge : bool option;
|
||||
useMasterOfSuffixAsName : bool option;
|
||||
}
|
||||
| Diseases of { useHalfLevelAsApValue : bool option }
|
||||
| Poisons of { useHalfLevelAsApValue : bool option }
|
||||
| Languages of { prerequisites : selectOptionPrerequisite list option }
|
||||
| MeleeCombatTechniques of skillWithoutGroups
|
||||
| RangedCombatTechniques of skillWithoutGroups
|
||||
| LiturgicalChants of skillWithoutGroups
|
||||
| Ceremonies of skillWithoutGroups
|
||||
| Skills of {
|
||||
groups : int list option;
|
||||
specific : specific option;
|
||||
prerequisites : increasablePrerequisite list option;
|
||||
apValue : increasableApValue option;
|
||||
}
|
||||
| Spells of skillWithoutGroups
|
||||
| Rituals of skillWithoutGroups
|
||||
|
||||
let t =
|
||||
Json_Decode_Strict.(
|
||||
field "category" string
|
||||
|> andThen (function
|
||||
| "Blessings" -> fun _ -> Blessings
|
||||
| "Cantrips" -> fun _ -> Cantrips
|
||||
| "TradeSecrets" -> fun _ -> TradeSecrets
|
||||
| "Scripts" -> fun _ -> Scripts
|
||||
| "AnimalShapes" -> fun _ -> AnimalShapes
|
||||
| "ArcaneBardTraditions" -> fun _ -> ArcaneBardTraditions
|
||||
| "ArcaneDancerTraditions" -> fun _ -> ArcaneDancerTraditions
|
||||
| "Elements" ->
|
||||
fun json -> Elements { specific = json |> specific }
|
||||
| "Properties" ->
|
||||
fun json ->
|
||||
Properties
|
||||
{
|
||||
requireKnowledge =
|
||||
json |> optionalField "requireKnowledge" bool;
|
||||
}
|
||||
| "Aspects" ->
|
||||
fun json ->
|
||||
Aspects
|
||||
{
|
||||
requireKnowledge =
|
||||
json |> optionalField "requireKnowledge" bool;
|
||||
useMasterOfSuffixAsName =
|
||||
json |> optionalField "useMasterOfSuffixAsName" bool;
|
||||
}
|
||||
| "Diseases" ->
|
||||
fun json ->
|
||||
Diseases
|
||||
{
|
||||
useHalfLevelAsApValue =
|
||||
json |> optionalField "useHalfLevelAsApValue" bool;
|
||||
}
|
||||
| "Poisons" ->
|
||||
fun json ->
|
||||
Poisons
|
||||
{
|
||||
useHalfLevelAsApValue =
|
||||
json |> optionalField "useHalfLevelAsApValue" bool;
|
||||
}
|
||||
| "Languages" ->
|
||||
fun json ->
|
||||
Languages
|
||||
{
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
(list selectOptionPrerequisite);
|
||||
}
|
||||
| "MeleeCombatTechniques" ->
|
||||
skillWithoutGroups |> map (fun x -> MeleeCombatTechniques x)
|
||||
| "RangedCombatTechniques" ->
|
||||
skillWithoutGroups |> map (fun x -> RangedCombatTechniques x)
|
||||
| "LiturgicalChants" ->
|
||||
skillWithoutGroups |> map (fun x -> LiturgicalChants x)
|
||||
| "Ceremonies" -> skillWithoutGroups |> map (fun x -> Ceremonies x)
|
||||
| "Skills" ->
|
||||
fun json ->
|
||||
Skills
|
||||
{
|
||||
groups = json |> optionalField "groups" (list int);
|
||||
specific = json |> specific;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
(list increasablePrerequisite);
|
||||
apValue =
|
||||
json |> optionalField "apValue" increasableApValue;
|
||||
}
|
||||
| "Spells" -> skillWithoutGroups |> map (fun x -> Spells x)
|
||||
| "Rituals" -> skillWithoutGroups |> map (fun x -> Rituals x)
|
||||
| str -> raise (DecodeError ("Unknown derived category: " ^ str))))
|
||||
|
||||
module Resolve = struct
|
||||
let insertIntoMap mp s = Map.insert s.id s mp
|
||||
|
||||
let mapInsert f staticmp mp =
|
||||
Ley_IntMap.foldr
|
||||
(fun static mp -> static |> f |> insertIntoMap mp)
|
||||
mp staticmp
|
||||
|
||||
let mapFilterMapInsert f filterMaps staticmp mp =
|
||||
Ley_IntMap.foldr
|
||||
(fun static mp ->
|
||||
let filterMap select_option =
|
||||
L.foldl'
|
||||
(fun g -> function Some acc -> g static acc | None -> None)
|
||||
filterMaps (Some select_option)
|
||||
in
|
||||
static |> f |> filterMap |> O.option mp (insertIntoMap mp))
|
||||
mp staticmp
|
||||
|
||||
let resolve_specific_ids specific id =
|
||||
match specific with
|
||||
| Some specific ->
|
||||
let filter id =
|
||||
match specific with
|
||||
| Include xs -> L.elem id xs
|
||||
| Exclude xs -> L.notElem id xs
|
||||
in
|
||||
fun static x -> if filter (id static) then Some x else None
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_require_knowledge require_knowledge special_ability_id id =
|
||||
let create_prerequisite entry_id =
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Activatable
|
||||
{
|
||||
id = special_ability_id;
|
||||
active = true;
|
||||
level = None;
|
||||
options = [ entry_id ];
|
||||
})
|
||||
in
|
||||
match require_knowledge with
|
||||
| Some true ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites = Plain [ create_prerequisite (id static) ];
|
||||
}
|
||||
| Some false | None -> F.const O.return
|
||||
|
||||
let resolve_half_ap_value use_half_ap_value =
|
||||
match use_half_ap_value with
|
||||
| Some true ->
|
||||
fun _ (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
(* AP value has been set to full level by conversion script *)
|
||||
O.Infix.(
|
||||
x.apValue >>= fun apValue ->
|
||||
Js.Int.toFloat apValue /. 2.0
|
||||
|> Js.Math.ceil_int |> O.return);
|
||||
}
|
||||
| Some false | None -> F.const O.return
|
||||
|
||||
let resolve_select_option_prerequisite { target; active; level } id =
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Activatable
|
||||
{ id = target; active; level; options = [ id ] })
|
||||
|
||||
let resolve_select_option_prerequisites prerequisites id =
|
||||
match prerequisites with
|
||||
| Some ps ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites =
|
||||
Plain
|
||||
( ps
|
||||
|> L.map
|
||||
(F.flip resolve_select_option_prerequisite
|
||||
(id static)) );
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_increasable_prerequisites prerequisites increasable_id
|
||||
select_option_id =
|
||||
let resolve_increasable_prerequisite static = function
|
||||
| Self sr ->
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Increasable
|
||||
{ id = increasable_id static; value = sr })
|
||||
| SelectOption options ->
|
||||
resolve_select_option_prerequisite options
|
||||
(select_option_id static)
|
||||
in
|
||||
match prerequisites with
|
||||
| Some ps ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites =
|
||||
Plain (ps |> L.map (resolve_increasable_prerequisite static));
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_ap_value id ic ap_value =
|
||||
match ap_value with
|
||||
| Some (DerivedFromIC modifier) ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
Some (ic static |> IC.getApForActivatation |> ( * ) modifier);
|
||||
}
|
||||
| Some (Fixed { list; default }) ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
Some
|
||||
( id static |> F.flip IM.lookup list
|
||||
|> O.fromOption default );
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata
|
||||
xs =
|
||||
Ley_List.foldl'
|
||||
(function
|
||||
| Blessings -> mapInsert Conversion.from_blessing blessings
|
||||
| Cantrips -> mapInsert Conversion.from_cantrip cantrips
|
||||
| TradeSecrets ->
|
||||
mapInsert Conversion.from_trade_secret trade_secrets
|
||||
| Scripts -> mapInsert Conversion.from_script scripts
|
||||
| AnimalShapes ->
|
||||
mapInsert
|
||||
(Conversion.from_animal_shape animal_shape_sizes src errata)
|
||||
animal_shapes
|
||||
| ArcaneBardTraditions ->
|
||||
mapInsert
|
||||
(Conversion.from_arcane_bard_tradition src errata)
|
||||
arcane_bard_traditions
|
||||
| ArcaneDancerTraditions ->
|
||||
mapInsert
|
||||
(Conversion.from_arcane_dancer_tradition src errata)
|
||||
arcane_dancer_traditions
|
||||
| Elements { specific } ->
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_element src errata)
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Element.t) -> x.id);
|
||||
]
|
||||
elements
|
||||
| Properties { requireKnowledge } ->
|
||||
let property_knowledge_id =
|
||||
Id.Activatable.MagicalSpecialAbility
|
||||
(Id.SpecialAbility.MagicalSpecialAbility.toInt
|
||||
PropertyKnowledge)
|
||||
in
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_property src errata)
|
||||
[
|
||||
resolve_require_knowledge requireKnowledge
|
||||
property_knowledge_id (fun (x : Property.t) ->
|
||||
Property x.id);
|
||||
]
|
||||
properties
|
||||
| Aspects { requireKnowledge; useMasterOfSuffixAsName } ->
|
||||
let aspect_knowledge_id =
|
||||
Id.Activatable.KarmaSpecialAbility
|
||||
(Id.SpecialAbility.KarmaSpecialAbility.toInt
|
||||
AspectKnowledge)
|
||||
in
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_aspect src errata)
|
||||
[
|
||||
resolve_require_knowledge requireKnowledge
|
||||
aspect_knowledge_id (fun (x : Aspect.t) -> Aspect x.id);
|
||||
( match useMasterOfSuffixAsName with
|
||||
| Some true ->
|
||||
fun { masterOfAspectSuffix; _ } x ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
name = masterOfAspectSuffix |> O.fromOption x.name;
|
||||
}
|
||||
| Some false | None -> F.const O.return );
|
||||
]
|
||||
aspects
|
||||
| Diseases { useHalfLevelAsApValue } ->
|
||||
mapFilterMapInsert Conversion.from_disease
|
||||
[ resolve_half_ap_value useHalfLevelAsApValue ]
|
||||
diseases
|
||||
| Poisons { useHalfLevelAsApValue } ->
|
||||
mapFilterMapInsert Conversion.from_poison
|
||||
[ resolve_half_ap_value useHalfLevelAsApValue ]
|
||||
poisons
|
||||
| Languages { prerequisites } ->
|
||||
mapFilterMapInsert Conversion.from_language
|
||||
[
|
||||
resolve_select_option_prerequisites prerequisites
|
||||
(fun (x : Language.t) -> Language x.id);
|
||||
]
|
||||
languages
|
||||
| MeleeCombatTechniques { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_melee_combat_technique
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : CombatTechnique.Melee.Static.t) ->
|
||||
MeleeCombatTechnique x.id)
|
||||
(fun (x : CombatTechnique.Melee.Static.t) ->
|
||||
MeleeCombatTechnique x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.id)
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
melee_combat_techniques
|
||||
| RangedCombatTechniques { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ranged_combat_technique
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) ->
|
||||
RangedCombatTechnique x.id)
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) ->
|
||||
RangedCombatTechnique x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.id)
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
ranged_combat_techniques
|
||||
| LiturgicalChants { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_liturgical_chant
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : LiturgicalChant.Static.t) ->
|
||||
LiturgicalChant x.id)
|
||||
(fun (x : LiturgicalChant.Static.t) ->
|
||||
LiturgicalChant x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.id)
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
liturgical_chants
|
||||
| Ceremonies { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ceremony
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : Ceremony.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Ceremony.Static.t) -> Ceremony x.id)
|
||||
(fun (x : Ceremony.Static.t) -> Ceremony x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Ceremony.Static.t) -> x.id)
|
||||
(fun (x : Ceremony.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
ceremonies
|
||||
| Skills { groups; specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_skill
|
||||
[
|
||||
( match groups with
|
||||
| Some included_groups ->
|
||||
fun static x ->
|
||||
if L.elem static.gr included_groups then Some x
|
||||
else None
|
||||
| None -> F.const O.return );
|
||||
resolve_specific_ids specific (fun (x : Skill.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Skill.Static.t) -> Skill x.id)
|
||||
(fun (x : Skill.Static.t) -> Skill x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Skill.Static.t) -> x.id)
|
||||
(fun (x : Skill.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
skills
|
||||
| Spells { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_spell
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Spell.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Spell.Static.t) -> Spell x.id)
|
||||
(fun (x : Spell.Static.t) -> Spell x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Spell.Static.t) -> x.id)
|
||||
(fun (x : Spell.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
spells
|
||||
| Rituals { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ritual
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Ritual.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Ritual.Static.t) -> Ritual x.id)
|
||||
(fun (x : Ritual.Static.t) -> Ritual x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Ritual.Static.t) -> x.id)
|
||||
(fun (x : Ritual.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
rituals)
|
||||
xs Map.empty
|
||||
end
|
||||
end
|
||||
|
||||
module Explicit = struct
|
||||
module Preset = struct
|
||||
module Translation = struct
|
||||
type t = { errata : Erratum.list option }
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{ errata = json |> optionalField "errata" Erratum.Decode.list }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (Translation)
|
||||
|
||||
type multilingual = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.General.Decode.multilingual option;
|
||||
apValue : int option;
|
||||
src : PublicationRef.Decode.multilingual list option;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial option;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" Id.Activatable.SelectOption.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.General.Decode.multilingual;
|
||||
apValue = json |> optionalField "apValue" int;
|
||||
src =
|
||||
json |> optionalField "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> optionalField "translations" TranslationMap.t;
|
||||
}
|
||||
|
||||
let make get_preset langs (multilingual : multilingual)
|
||||
(maybe_translation : Translation.t option) =
|
||||
O.Infix.(
|
||||
get_preset multilingual.id <&> fun (preset : t) ->
|
||||
{
|
||||
preset with
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
<&> Prerequisite.Collection.General.Decode.resolveTranslations
|
||||
langs
|
||||
|> O.fromOption (Prerequisite.Collection.ByLevel.Plain []);
|
||||
apValue = multilingual.apValue <|> preset.apValue;
|
||||
src =
|
||||
multilingual.src
|
||||
|> O.option preset.src
|
||||
(PublicationRef.Decode.resolveTranslationsList langs);
|
||||
errata =
|
||||
maybe_translation
|
||||
>>= (fun translation -> translation.errata)
|
||||
|> O.fromOption preset.errata;
|
||||
})
|
||||
|
||||
let resolveTranslations get_preset langs (x : multilingual) =
|
||||
Ley_Option.Infix.(
|
||||
x.translations
|
||||
>>= TranslationMap.getFromLanguageOrder langs
|
||||
|> make get_preset langs x)
|
||||
|
||||
let resolveId ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata =
|
||||
O.Infix.(
|
||||
function
|
||||
| Id.Activatable.SelectOption.Generic _ -> None
|
||||
| Blessing id ->
|
||||
blessings |> Ley_IntMap.lookup id <&> Conversion.from_blessing
|
||||
| Cantrip id ->
|
||||
cantrips |> Ley_IntMap.lookup id <&> Conversion.from_cantrip
|
||||
| TradeSecret id ->
|
||||
trade_secrets |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_trade_secret
|
||||
| Language id ->
|
||||
languages |> Ley_IntMap.lookup id <&> Conversion.from_language
|
||||
| Script id ->
|
||||
scripts |> Ley_IntMap.lookup id <&> Conversion.from_script
|
||||
| AnimalShape id ->
|
||||
animal_shapes |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_animal_shape animal_shape_sizes src errata
|
||||
| ArcaneBardTradition id ->
|
||||
arcane_bard_traditions |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_arcane_bard_tradition src errata
|
||||
| ArcaneDancerTradition id ->
|
||||
arcane_dancer_traditions |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_arcane_dancer_tradition src errata
|
||||
| Element id ->
|
||||
elements |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_element src errata
|
||||
| Property id ->
|
||||
properties |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_property src errata
|
||||
| Aspect id ->
|
||||
aspects |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_aspect src errata
|
||||
| Disease id ->
|
||||
diseases |> Ley_IntMap.lookup id <&> Conversion.from_disease
|
||||
| Poison id ->
|
||||
poisons |> Ley_IntMap.lookup id <&> Conversion.from_poison
|
||||
| MeleeCombatTechnique id ->
|
||||
melee_combat_techniques |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_melee_combat_technique
|
||||
| RangedCombatTechnique id ->
|
||||
ranged_combat_techniques |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_ranged_combat_technique
|
||||
| LiturgicalChant id ->
|
||||
liturgical_chants |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_liturgical_chant
|
||||
| Ceremony id ->
|
||||
ceremonies |> Ley_IntMap.lookup id <&> Conversion.from_ceremony
|
||||
| Skill id -> skills |> Ley_IntMap.lookup id <&> Conversion.from_skill
|
||||
| Spell id -> spells |> Ley_IntMap.lookup id <&> Conversion.from_spell
|
||||
| Ritual id ->
|
||||
rituals |> Ley_IntMap.lookup id <&> Conversion.from_ritual)
|
||||
end
|
||||
|
||||
module Custom = struct
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
description : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
description = json |> optionalField "description" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (Translation)
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.General.Decode.multilingual option;
|
||||
apValue : int option;
|
||||
src : PublicationRef.Decode.multilingual list option;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.General.Decode.multilingual;
|
||||
apValue = json |> optionalField "apValue" int;
|
||||
src =
|
||||
json |> optionalField "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" TranslationMap.t;
|
||||
}
|
||||
|
||||
let make main_src langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
O.Infix.
|
||||
{
|
||||
id = Generic multilingual.id;
|
||||
name = translation.name;
|
||||
description = translation.description;
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
<&> Prerequisite.Collection.General.Decode.resolveTranslations
|
||||
langs
|
||||
|> O.fromOption (Prerequisite.Collection.ByLevel.Plain []);
|
||||
apValue = multilingual.apValue;
|
||||
isSecret = None;
|
||||
languages = None;
|
||||
continent = None;
|
||||
isExtinct = None;
|
||||
specializations = None;
|
||||
specializationInput = None;
|
||||
animalGr = None;
|
||||
animalLevel = None;
|
||||
enhancementTarget = None;
|
||||
enhancementLevel = None;
|
||||
applications = None;
|
||||
staticEntry = None;
|
||||
src =
|
||||
multilingual.src
|
||||
|> O.option main_src
|
||||
(PublicationRef.Decode.resolveTranslationsList langs);
|
||||
errata = translation.errata |> O.fromOption [];
|
||||
}
|
||||
|
||||
let resolveTranslations main_src langs x =
|
||||
Ley_Option.Infix.(
|
||||
x.translations
|
||||
|> TranslationMap.getFromLanguageOrder langs
|
||||
<&> make main_src langs x)
|
||||
end
|
||||
|
||||
type multilingual =
|
||||
| Preset of Preset.multilingual
|
||||
| Custom of Custom.multilingual
|
||||
|
||||
let multilingual =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Preset" ->
|
||||
field "value" Preset.multilingual |> map (fun x -> Preset x)
|
||||
| "Custom" ->
|
||||
field "value" Custom.multilingual |> map (fun x -> Custom x)
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError ("Unknown explicit select option type: " ^ str))))
|
||||
|
||||
let resolveTranslations get_preset langs main_src = function
|
||||
| Preset multilingual ->
|
||||
multilingual |> Preset.resolveTranslations get_preset langs
|
||||
| Custom multilingual ->
|
||||
multilingual |> Custom.resolveTranslations main_src langs
|
||||
|
||||
let resolve get_preset langs main_src xs =
|
||||
let fold x =
|
||||
x
|
||||
|> resolveTranslations get_preset langs main_src
|
||||
|> O.option F.id (fun value -> Map.insert value.id value)
|
||||
in
|
||||
Ley_List.foldl' fold xs Map.empty
|
||||
end
|
||||
|
||||
type multilingual =
|
||||
| Derived of Derived.t list
|
||||
| Explicit of Explicit.multilingual list
|
||||
|
||||
let multilingual =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Derived" ->
|
||||
field "value" (list Derived.t) |> map (fun x -> Derived x)
|
||||
| "Explicit" ->
|
||||
field "value" (list Explicit.multilingual)
|
||||
|> map (fun x -> Explicit x)
|
||||
| str -> raise (DecodeError ("Unknown select option type: " ^ str))))
|
||||
|
||||
let resolve ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata langs
|
||||
= function
|
||||
| Derived derived ->
|
||||
Derived.Resolve.resolve ~blessings ~cantrips ~trade_secrets ~languages
|
||||
~scripts ~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata
|
||||
derived
|
||||
| Explicit explicit ->
|
||||
Explicit.resolve
|
||||
(Explicit.Preset.resolveId ~blessings ~cantrips ~trade_secrets
|
||||
~languages ~scripts ~animal_shapes ~animal_shape_sizes
|
||||
~arcane_bard_traditions ~arcane_dancer_traditions ~elements
|
||||
~properties ~aspects ~diseases ~poisons ~melee_combat_techniques
|
||||
~ranged_combat_techniques ~liturgical_chants ~ceremonies ~skills
|
||||
~spells ~rituals ~src ~errata)
|
||||
langs src explicit
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
type staticEntry =
|
||||
| Blessing of Blessing.Static.t
|
||||
| Cantrip of Cantrip.Static.t
|
||||
| TradeSecret of TradeSecret.t
|
||||
| Language of Language.t
|
||||
| Script of Script.t
|
||||
| AnimalShape of AnimalShape.t
|
||||
| SpellEnhancement of Enhancement.t
|
||||
| LiturgicalChantEnhancement of Enhancement.t
|
||||
| ArcaneBardTradition of ArcaneTradition.t
|
||||
| ArcaneDancerTradition of ArcaneTradition.t
|
||||
| Element of Element.t
|
||||
| Property of Property.t
|
||||
| Aspect of Aspect.t
|
||||
| Disease of Disease.t
|
||||
| Poison of Poison.t
|
||||
| MeleeCombatTechnique of CombatTechnique.Melee.Static.t
|
||||
| RangedCombatTechnique of CombatTechnique.Ranged.Static.t
|
||||
| LiturgicalChant of LiturgicalChant.Static.t
|
||||
| Ceremony of Ceremony.Static.t
|
||||
| Skill of Skill.Static.t
|
||||
| Spell of Spell.Static.t
|
||||
| Ritual of Ritual.Static.t
|
||||
|
||||
type t = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
name : string;
|
||||
apValue : int option;
|
||||
prerequisites : Prerequisite.Collection.General.t;
|
||||
description : string option;
|
||||
isSecret : bool option;
|
||||
languages : int list option;
|
||||
continent : int option;
|
||||
isExtinct : bool option;
|
||||
specializations : string list option;
|
||||
specializationInput : string option;
|
||||
animalGr : int option;
|
||||
animalLevel : int option;
|
||||
enhancementTarget : int option;
|
||||
enhancementLevel : int option;
|
||||
staticEntry : staticEntry option;
|
||||
applications : Skill.Static.Application.t list option;
|
||||
(** needed to be able to filter valid applications without altering the
|
||||
static entry *)
|
||||
src : PublicationRef.t list;
|
||||
errata : Erratum.t list;
|
||||
}
|
||||
|
||||
module Map : Ley_Map.T with type key = Id.Activatable.SelectOption.t
|
||||
|
||||
type map = t Map.t
|
||||
|
||||
module Decode : sig
|
||||
type multilingual
|
||||
|
||||
val multilingual : multilingual Json.Decode.decoder
|
||||
|
||||
val resolve :
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:Property.t Ley_IntMap.t ->
|
||||
aspects:Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
src:PublicationRef.list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
map
|
||||
end
|
||||
@@ -0,0 +1,160 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Shared.CombatSpecialAbilityType.t;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
combatTechniques : Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
penalty = json |> optionalField "penalty" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Shared.CombatSpecialAbilityType.t;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
combatTechniques : Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
category =
|
||||
json |> field "max" Shared.CombatSpecialAbilityType.Decode.t;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
combatTechniques =
|
||||
json
|
||||
|> field "combatTechniques"
|
||||
Shared.ApplicableCombatTechniques.Decode.t;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
category = multilingual.category;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
penalty = translation.penalty;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
combatTechniques = multilingual.combatTechniques;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,30 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Activatable_Shared.CombatSpecialAbilityType.t;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
combatTechniques : Activatable_Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,144 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,27 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,144 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,27 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user