Compare commits

...

81 Commits

Author SHA1 Message Date
Lukas Obermann c8fac07d76 unstaged id changes 2023-01-17 20:16:52 +01:00
elyukai 593eb4dba7 refactor: finished Profession dynamic entry and style 2021-01-07 23:29:34 +01:00
elyukai b1373e6aaf refactor: converted Profession 2021-01-07 16:52:55 +01:00
elyukai accc5a4ea5 refactor: converted and adjusted Culture 2021-01-07 02:21:19 +01:00
elyukai 3eb61cc55c refactor: worked on dependencies and warnings in OCaml 2021-01-07 02:01:05 +01:00
elyukai f7e5e06ff2 refactor: parser almost done 2021-01-05 03:05:06 +01:00
elyukai 35048e1032 refactor: created all special ability entities 2021-01-04 22:12:38 +01:00
elyukai 9ee4626f49 refactor: finished new select option conversion 2021-01-04 18:45:11 +01:00
elyukai 4f698af39b refactor: more conversions to OCaml 2021-01-02 02:48:35 +01:00
elyukai 7e055864a4 refactor: started refactoring Prerequisite 2021-01-01 18:45:45 +01:00
elyukai 669e2cb1b0 refactor: some more conversions to OCaml 2020-12-31 01:14:25 +01:00
elyukai b6971d190b refactor: further OCaml conversions 2020-12-29 21:57:09 +01:00
elyukai b0f1869886 feat: added OCaml and started converting to new schema 2020-12-29 21:31:43 +01:00
elyukai 74f7811f18 build: exclude BuckleScript output from Git 2020-12-27 16:55:15 +01:00
elyukai 66a5329331 build: remove BuckleScript output from Git 2020-12-27 16:54:10 +01:00
Lukas Obermann dfe9e37ae9 test: started rewriting some old tests for modules 2020-11-26 02:03:42 +01:00
Lukas Obermann c41d3c4912 refactor: active Activatable AP calculation done 2020-11-25 20:04:42 +01:00
Lukas Obermann 956cf3958d refactor: extract activatable type into own module 2020-11-25 12:41:59 +01:00
Lukas Obermann 1e31e7b8de refactor: basic init progress visualization 2020-11-24 02:28:25 +01:00
Lukas Obermann 6776e9582a refactor: main process
This completes the implementation of main process features. It features
basic window handling, data initialization and adjustments of decoders
to match the database and it's schemes.
A very rough renderer process script has been added to avoid depedencies
with errors to be used as well as to be able to test ipc.
2020-11-24 01:42:00 +01:00
Lukas Obermann e7e02c025c refactor: started writing worker thread bindings 2020-11-20 23:52:37 +01:00
Lukas Obermann e0b8625a4b refactor: converted app config to ReasonML 2020-11-20 22:54:02 +01:00
Lukas Obermann 7ee370c687 refactor: converted main script to ReasonML 2020-11-20 01:06:41 +01:00
Lukas Obermann ced296b47f refactor: use plain variants instead of pairs 2020-11-17 01:49:35 +01:00
Lukas Obermann 832da43ecd build: adjusted incoming config
For the rework large parts of the folder structure were changed and this
needs to be reflected in new build tooling configuration.
2020-11-14 01:41:14 +01:00
Lukas Obermann a62195f390 build: applied development changes to database 2020-11-13 16:56:42 +01:00
Lukas Obermann 4da9621b3e build: upgraded Reason output 2020-11-13 15:43:12 +01:00
Lukas Obermann e15bf1784c Merge branch 'develop' into feature/ama3 2020-11-13 15:04:07 +01:00
Lukas Obermann be7e174353 Adjusted parser to fit database schema fixes 2020-10-08 16:01:26 +02:00
Lukas Obermann 91f6ccbdaa Synced parser with schemes except for item schemes 2020-10-07 01:17:43 +02:00
Lukas Obermann 6bee4968cf Changed structure for missing modules 2020-10-05 17:11:53 +02:00
Lukas Obermann 51b2fd85fe Some decoder restructuring 2020-10-05 15:06:19 +02:00
Lukas Obermann 785366f25a Added explanation for dynamic and static 2020-10-05 12:12:51 +02:00
Lukas Obermann 2e488573f0 Finished basic decoding docs 2020-10-05 12:06:43 +02:00
Lukas Obermann a91241b759 Started adding docs for decoder 2020-10-05 04:29:25 +02:00
Lukas Obermann 13e90fe2ca Hide language order implementation 2020-10-05 03:36:44 +02:00
Lukas Obermann 0da83cb257 Removed app folder and integrated content into src and docs folder 2020-10-05 01:29:38 +02:00
Lukas Obermann 0fadd70dbd Moved database to src folder 2020-10-05 01:23:21 +02:00
Lukas Obermann cbcebc8722 Basic rework of parser done
Compiler without errors
2020-10-05 01:06:49 +02:00
Lukas Obermann 5b2be2aba6 Some data modules simplifications
Reworked decoder for select options
2020-10-04 01:18:35 +02:00
Lukas Obermann fb26257254 Reworked prerequisites 2020-10-02 02:43:56 +02:00
Lukas Obermann 6141671c11 Converted all skills and some other entries to new schema 2020-10-01 16:01:10 +02:00
Lukas Obermann 8983c129be Started reworking JSON schema 2020-10-01 01:29:08 +02:00
Lukas Obermann f0fedb2ee5 Refactores Attributes and Skills 2020-09-30 01:32:50 +02:00
Lukas Obermann a8cc0883df Switched database branch 2020-09-29 22:04:43 +02:00
Lukas Obermann 0820860125 Merge branch 'develop' into feature/ama3 2020-09-29 22:04:11 +02:00
Lukas Obermann 635c11cda2 Finished id rework 2020-07-20 02:25:58 +02:00
Lukas Obermann 57032d78bc Started reworking ids 2020-07-20 00:28:01 +02:00
Lukas Obermann a89fd07252 Started adding AP spent difference function 2020-07-15 11:31:01 +02:00
Lukas Obermann f2380eb9b4 Finished single activatable active entry generation 2020-07-14 01:45:37 +02:00
Lukas Obermann 3348c08808 Finished active validation 2020-07-14 01:39:50 +02:00
Lukas Obermann 4492304173 Finished single inactive entry generation and validation 2020-07-12 20:22:53 +02:00
Lukas Obermann 9acffd878e Finished inactive validation functions for validation details 2020-07-12 13:43:36 +02:00
Lukas Obermann 1667683ceb Stricter compiler config
Started adding inactive validation
2020-07-12 01:51:18 +02:00
Lukas Obermann ae44828365 Started working on full inactive activatable generation 2020-07-10 00:26:11 +02:00
Lukas Obermann 3f48a8f42d Finished function to get available select options for inactive activatable entry 2020-07-08 22:09:46 +02:00
Lukas Obermann 5d341bb6b6 Removed genType as it's not needed currently 2020-07-08 15:11:26 +02:00
Lukas Obermann d4f15baf96 Restructures folders for Reason files 2020-07-08 15:07:23 +02:00
Lukas Obermann bfd1f05c89 Updated Reason libraries
Started converting function to get valid select options for inactive A/D/SA to Reason
2020-07-07 00:22:49 +02:00
Lukas Obermann de582f9140 Merged source ref grouping and availability checks 2020-07-01 15:41:57 +02:00
Lukas Obermann f79c84caeb Added transducers and source ref groups 2020-06-25 01:05:47 +02:00
Lukas Obermann 521bca34fe Splitted Activatable module into separate files 2020-06-15 23:15:54 +02:00
elyukai d616157397 Finished Extended Style conversion 2020-06-15 01:31:17 +02:00
elyukai b8cd0794ca Added more documentation to public functions
Started converting ExtendedStyleUtils
Removed more converted TS code
2020-06-14 01:03:38 +02:00
elyukai 7ec14231e0 Finished Dependencies module 2020-06-13 20:34:28 +02:00
elyukai 053fcb2874 Finished Prerequisites module
Started working on Dependencies
Removed some TS code that has been converted to Reason
2020-06-12 23:40:24 +02:00
elyukai 9b7948f6f6 Started converting prerequisite validators 2020-06-12 00:46:51 +02:00
elyukai d31be41a12 Started converting prerequisite functions to Reason 2020-06-12 00:31:34 +02:00
Lukas Obermann 117a732594 Started to convert actions 2020-05-30 00:39:27 +02:00
Lukas Obermann 6fe2c3323f Reworked models to fit Reason generated types 2020-05-28 23:20:21 +02:00
Lukas Obermann b888851995 Finished Dropdown 2020-05-25 21:36:36 +02:00
Lukas Obermann 9b95187936 Merge branch 'develop' into feature/ama3 2020-05-25 15:06:13 +02:00
Lukas Obermann 283825d6d9 Added Switch and ScrollView and started to add Dropdowns in Reason 2020-05-25 11:59:44 +02:00
Lukas Obermann 8da9b6f641 Added ReactUtils for easier reuse of commonly used patterns 2020-05-21 13:41:40 +02:00
Lukas Obermann 2b20c8598a Added some more UI views 2020-05-21 00:34:17 +02:00
Lukas Obermann 58df332c06 Started to add some universal React components 2020-05-19 02:07:13 +02:00
Lukas Obermann b1f9a155f9 Synced TS data structures with RE data structures
Started reworking TS files to match the TS data structure definitions
There are still A LOT of errors due to this
2020-05-12 01:02:39 +02:00
Lukas Obermann 2bc235292c Merge part 2 2020-05-09 22:10:02 +02:00
Lukas Obermann 1eae92cabc Merge branch 'develop' into feature/ama3 2020-05-09 22:07:12 +02:00
Lukas Obermann df6c9c310e Merge branch 'develop' into feature/ama3 2020-03-06 01:49:51 +01:00
Lukas Obermann e653063350 Started trying to convert YAML parser to Reason 2020-03-04 15:38:13 +01:00
977 changed files with 68150 additions and 61198 deletions
+4
View File
@@ -1,3 +1,4 @@
_opam
node_modules/
npm-debug.log*
.vs/
@@ -16,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
View File
@@ -1,3 +1,3 @@
[submodule "app/Database"]
path = app/Database
path = src/Database
url = https://github.com/elyukai/optolith-data.git
+24
View File
@@ -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
View File
@@ -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>
+2
View File
@@ -0,0 +1,2 @@
break-infix-before-func = true
break-collection-expressions = wrap
+5 -1
View File
@@ -17,5 +17,9 @@
"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.sandbox": {
"kind": "opam",
"switch": "${workspaceFolder:optolith-client-rework}"
}
}
+16 -59
View File
@@ -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": []
}
},
]
}
Submodule app/Database deleted from f2064cea3e
-5
View File
@@ -1,5 +0,0 @@
const _require = require("esm")(module)
process.once('loaded', () => {
global.require = _require
})
+26 -14
View File
@@ -4,6 +4,8 @@
"sources": [
{ "dir": "deploy" },
{ "dir": "src" },
{ "dir": "src/Activatable" },
{ "dir": "src/Activatable/SpecialAbilities" },
{ "dir": "src/App/Actions" },
{ "dir": "src/App/Constants" },
{ "dir": "src/App/Containers" },
@@ -15,12 +17,25 @@
{ "dir": "src/App/Utilities/YAML" },
{ "dir": "src/App/Views" },
{ "dir": "src/App/Views/Universal" },
{ "dir": "src/Cloud" },
{ "dir": "src/Control" },
{ "dir": "src/Data" },
{ "dir": "src/Data/__tests__", "type": "dev" },
{ "dir": "src/Debug" },
{ "dir": "src/System" },
{ "dir": "src/shims" }
{ "dir": "src/Equipment" },
{ "dir": "src/ExternalBindings" },
{ "dir": "src/Increasable" },
{ "dir": "src/Increasable/__tests__", "type": "dev" },
{ "dir": "src/Increasable/MagicalActions" },
{ "dir": "src/Init" },
{ "dir": "src/Misc" },
{ "dir": "src/Misc/__tests__", "type": "dev" },
{ "dir": "src/Prerequisites" },
{ "dir": "src/RaceCultureProfession" },
{ "dir": "src/Rules" },
{ "dir": "src/Sources" },
{ "dir": "src/Utilities" },
{ "dir": "src/Utilities/__tests__", "type": "dev" }
],
"package-specs": {
"module": "commonjs",
@@ -30,22 +45,19 @@
"bs-dependencies": [
"@glennsl/bs-json",
"@glennsl/bs-jest",
"reason-react"
"bs-fetch",
"bs-webapi",
"decco",
"reason-react",
"reductive"
],
"ppx-flags": [ "decco/ppx" ],
"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
}
+113
View File
@@ -0,0 +1,113 @@
# 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
### Basic decoder structure
All decodable 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.
```re
// 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 = {
module Translation = {
type t = {
// ... (language-specific properties)
};
let t = Json.Decode.{
// ... (decode language-specific properties)
};
}
module TranslationMap = TranslationMap.Make(Translation);
type multilingual = {
// ... (language-independent properties)
translations: TranslationMap.t,
}
let multilingual = json =>
Json.Decode.{
// ... (decode language-independent properties)
translations: json |> field("translations", TranslationMap.Decode.t),
};
let resolveTranslations = (langs, x) =>
Ley_Option.Infix.(
x.translations
|> TranslationMap.Decode.getFromLanguageOrder(langs)
<&> (translation => {
// ... (merge language-independent and language-specific properties)
})
);
let t = (langs, json) =>
json |> multilingual |> resolveTranslations(langs);
};
```
### The two steps of decoding
All *static types* (i.e., all data defined in YAML files) share a common structure concerning their decode process.
#### 1. Decode a multilingual entry
Since language-specific values in the YAML files are always defined in dictionaries, we always split the language-specific and the language-independent parts.
The language-specific part of a type is always defined in a separate module `Translation` as record `t`. The `Translation` module is used for the functor `TranslationMap.Make`, which creates functions to decode the dictionaries and to resolve them later (see next step).
The language-independent part of a type is defined as record `multilingual` and always has the property `translations`, which is the `TranslationMap` type created by the `TranslationMap.Make` functor.
Now we can decode all language-independent values and the `TranslationMap`. Using the `Decode.t` function on the `TranslationMap`, it is not completely decoded, but instead only which languages are available, which is important because we need to check whether this entry is available in one of the selected languages. This then almost mirrors the structure of the data in the YAML files.
The corresponding function is called `multilingual`.
#### 2. Resolve translation maps with language settings
In the second steps, the translations maps are reduced to one language to form the main type together with the language-independent values.
By making use of the `Decode.getFromLanguageOrder` function on the translation map, we can get the values in the most preferred language and decode them at once. Since it returns an `Option`, we can safely continue only if a matching language has been found. Then we can build the main record based on the decoded language-specific part and the language-independent part.
The corresponding function is called `resolveTranslations`.
Together, `multilingual` and `resolveTranslations` form the `t` decoder function for the main type.
### Main types
In order to be used easier, an `assoc` auxiliary decoder function should be created to return a pair of the entry's id as well as the entry itself, since then it is easier to build a map of them. Some nested types use that, too.
### Nested types
Some types have nested types that have own translations (like professions and their variants). In those cases, you basically take the same steps mentioned above, although you can skip creating a main `t` decoder since that can't be used.
Instead, the main type's `multilingual` type references the nested type's `multilingual` and the `multilingual` decoder uses the nested type's `multilingual` decoder.
In step 2, it is basically the same: `resolveTranslations` of the nested type is called in `resolveTranslations` of the main type.
### Visibility conventions
For main types, only the main type itself and it's `t` (or `assoc`) function should be exposed. For nested types in the same module (as submodules), you can hide all decode types and functions. For nested types in a different module, you'll need to expose the `multilingual` and `resolveTranslations` functions and thus the `multilingual` type, which should always be abstract, since it should never be changed outside of the module.
As a rule of thumb: Just export what is *really* needed and also always check if a decode type can be made abstract.
### Naming conventions
- The language-specific module is always named `Translation` and contains a type `t` with a corresponding decoder `t` (the latter two are a requirement for the `TranslationMap.Make` functor anyway).
- The built translation map module is called `TranslationMap`.
- 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 `resolveTranslations`.
- The function combining `multilingual` and `resolveTranslations` is called `t`, since it decodes the type `t`.
You can, of course, always create additional helper functions for `multilingual`, `resolveTranslations` and `decode` if your type is usually in a container, then you need to provide a proper suffix for the function as well, so you basically get, for example, `multilingual` and `multilingualList` (if your type is in a list). Exception: If your type is `t`, just use the suffix instead, for example, `t` and `assoc` (if your type is used in a map) then.
+8
View File
@@ -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?
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
transform: {
"^.+\\.tsx?$": "ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(j|t)sx?$",
moduleFileExtensions: [
"ts",
"tsx",
+18121 -4087
View File
File diff suppressed because it is too large Load Diff
+65 -62
View File
@@ -9,73 +9,76 @@
},
"homepage": "https://github.com/elyukai/optolith-client",
"license": "MPL-2.0",
"main": "app/main.js",
"main": "dist/main.js",
"dependencies": {
"@glennsl/bs-json": "5.0.2",
"ajv": "6.12.6",
"bs-platform": "8.3.2",
"electron-devtools-installer": "3.1.1",
"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": "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.1",
"react-textfit": "1.1.0",
"reason-react": "0.9.1",
"reductive": "2.0.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.3.1",
"tslib": "1.11.2",
"yaml": "1.10.0"
"@glennsl/bs-jest": "^0.7.0",
"@glennsl/bs-json": "^5.0.2",
"ajv": "^6.12.6",
"bs-fetch": "^0.6.2",
"bs-platform": "^9.0.2",
"bs-webapi": "^0.19.1",
"decco": "^1.4.0",
"electron-devtools-installer": "^3.1.1",
"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": "^4.3.1",
"react-portal": "^4.2.1",
"react-redux": "^7.2.1",
"react-textfit": "^1.1.0",
"reason-react": "^0.9.1",
"reductive": "^2.1.1",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"relude": "^0.63.1",
"remark-breaks": "^1.0.5",
"reselect": "^4.0.0",
"semver": "^7.3.2",
"ssh2-sftp-client": "^5.3.1",
"tslib": "^1.11.2",
"yaml": "^1.10.0"
},
"devDependencies": {
"@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "11.0.0",
"@glennsl/bs-jest": "0.6.0",
"@types/classnames": "2.2.11",
"@types/jest": "26.0.15",
"@types/mousetrap": "1.6.4",
"@types/node": "14.14.7",
"@types/react": "16.9.56",
"@types/react-custom-scrollbars": "4.0.7",
"@types/react-dom": "16.9.9",
"@types/react-portal": "4.0.2",
"@types/react-redux": "7.1.11",
"@types/semver": "7.3.4",
"@typescript-eslint/eslint-plugin": "4.7.0",
"@typescript-eslint/parser": "4.7.0",
"@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",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"custom-env": "2.0.1",
"dotenv": "8.2.0",
"electron": "10.1.5",
"electron-builder": "22.9.1",
"electron-notarize": "0.3.0",
"eslint": "7.13.0",
"eslint-plugin-react": "7.21.5",
"eslint-plugin-react-hooks": "4.2.0",
"fs-extra": "9.0.1",
"gentype": "3.38.0",
"html-webpack-plugin": "4.5.0",
"husky": "4.3.0",
"jest": "26.4.2",
"electron": "^10.4.3",
"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",
"fs-extra": "9.1.0",
"gentype": "^3.48.0",
"html-webpack-plugin": "^5.3.1",
"husky": "^4.3.0",
"jest": "^26.6.3",
"qusly-core": "2.5.2",
"sass": "1.29.0",
"standard-version": "9.0.0",
"ts-jest": "26.4.4",
"ts-loader": "8.0.11",
"sass": "^1.32.11",
"standard-version": "^9.2.0",
"ts-jest": "^26.5.5",
"ts-loader": "^9.1.1",
"typescript": "3.8.3",
"webpack": "5.4.0",
"webpack-cli": "4.2.0"
"webpack": "^5.35.1",
"webpack-cli": "^4.6.0"
},
"scripts": {
"prerelease:win": "set NODE_ENV=development && node ./deploy/buildInsiderWin.js",
@@ -84,8 +87,8 @@
"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": "sass --style=compressed src/Main.scss app/main.css",
"css:watch": "sass --watch --style=compressed src/Main.scss app/main.css",
"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",
@@ -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)
+199
View File
@@ -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)))
// }
// }
+5
View File
@@ -0,0 +1,5 @@
type action =
| SetCulture(int)
| SetCulturesSortOrder(FilterOptions.culturesSortOptions)
| SetCulturesVisibilityFilter(FilterOptions.culturesVisibilityFilter)
| SetCulturesFilterText(string);
+50
View File
@@ -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
+349
View File
@@ -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
+37
View File
@@ -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
+60
View File
@@ -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,
};
};
+27
View File
@@ -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;
+507
View File
@@ -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;
+35
View File
@@ -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),
};
+109
View File
@@ -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
+29
View File
@@ -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
+64
View File
@@ -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;
+313
View File
@@ -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);
};
};
+34
View File
@@ -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);
+58
View File
@@ -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,
);
+54
View File
@@ -0,0 +1,54 @@
/**
* `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('a)),
option(Activatable_Dynamic.t('a))
) =>
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('a)),
option(Activatable_Dynamic.t('a))
) =>
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('a)),
option(Activatable_Dynamic.t('a))
) =>
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('a))),
list(option(Activatable_Dynamic.t('a)))
) =>
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);
+464
View File
@@ -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
+335
View File
@@ -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
+164
View File
@@ -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)
+31
View File
@@ -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
+64
View File
@@ -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
+50
View File
@@ -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)
+9
View File
@@ -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
+74
View File
@@ -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
+16
View File
@@ -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
+94
View File
@@ -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
+19
View File
@@ -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
+164
View File
@@ -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)
+31
View File
@@ -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
+970
View File
@@ -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
+85
View File
@@ -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
@@ -0,0 +1,148 @@
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;
aeCost : string;
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;
aeCost : string;
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;
aeCost = json |> field "aeCost" Shared.AeCost.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;
aeCost = translation.aeCost;
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,28 @@
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;
aeCost : string;
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,161 @@
module Shared = Activatable_Shared
module Static = struct
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Shared.Property.t;
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;
effect : string;
input : string option;
aeCost : string option;
volume : string;
bindingCost : 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;
effect = json |> field "effect" Shared.Effect.Decode.t;
input = json |> optionalField "input" Shared.Input.Decode.t;
aeCost = json |> optionalField "aeCost" Shared.AeCost.Decode.t;
volume = json |> field "volume" Shared.Volume.Decode.t;
bindingCost =
json |> optionalField "bindingCost" Shared.BindingCost.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;
property : Shared.Property.t;
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;
property = json |> field "property" Shared.Property.Decode.t;
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;
effect = translation.effect;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
aeCost = translation.aeCost;
volume = translation.volume;
bindingCost = translation.bindingCost;
property = multilingual.property;
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,31 @@
module Static : sig
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Activatable_Shared.Property.t;
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,235 @@
module IM = Ley_IntMap
module O = Ley_Option
module Shared = Activatable_Shared
module Static = struct
type specialRule =
| Labeled of { label : string; text : string }
| Plain of string
type favoredCombatTechniques =
| All
| AllMelee
| AllUsedInHunting
| Specific of Id.CombatTechnique.t list
type favoredSkillsSelection = { amount : int; options : int list }
type t = {
id : int;
name : string;
nameShort : string option;
nameInWiki : string option;
levels : int option;
max : int option;
specialRules : specialRule list;
selectOptions : SelectOption.map;
input : string option;
primary : int;
aspects : (int * int) option;
restrictedBlessings : int list;
favoredCombatTechniques : favoredCombatTechniques option;
favoredSkills : int list;
favoredSkillsSelection : favoredSkillsSelection option;
isShamanistic : bool;
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
module F = Ley_Function
type nonrec t = t
module Translation = struct
let specialRule =
Json_Decode_Strict.(
field "text" string
|> andThen (fun text ->
optionalField "label" string
|> map (function
| Some label -> Labeled { label; text }
| None -> Plain text)))
type t = {
name : string;
nameShort : string option;
nameInWiki : string option;
specialRules : specialRule list;
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;
nameShort = json |> optionalField "nameShort" string;
nameInWiki =
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
specialRules = json |> field "specialRules" (list specialRule);
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
let favoredCombatTechniques =
Json.Decode.(
field "type" string
|> andThen (function
| "All" -> F.const All
| "AllMelee" -> F.const AllMelee
| "AllUsedInHunting" -> F.const AllUsedInHunting
| "List" ->
field "value" (list Id.CombatTechnique.Decode.t)
|> map (fun xs -> Specific xs)
| str ->
raise
(DecodeError
("Unknown favored combat techniques type: " ^ str))))
let favoredSkillsSelection json =
Json.Decode.
{
amount = json |> field "amount" int;
options = json |> field "options" (list int);
}
type multilingual = {
id : int;
levels : int option;
max : int option;
selectOptions : SelectOption.Decode.multilingual option;
primary : int;
aspects : (int * int) option;
restrictedBlessings : int list option;
favoredCombatTechniques : favoredCombatTechniques option;
favoredSkills : int list;
favoredSkillsSelection : favoredSkillsSelection option;
isShamanistic : bool 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;
primary = json |> field "primary" int;
aspects = json |> optionalField "aspects" (tuple2 int int);
restrictedBlessings =
json |> optionalField "restrictedBlessings" (list int);
favoredCombatTechniques =
json
|> optionalField "favoredCombatTechniques" favoredCombatTechniques;
favoredSkills = json |> field "favoredSkills" (list int);
favoredSkillsSelection =
json
|> optionalField "favoredSkillsSelection" favoredSkillsSelection;
isShamanistic = json |> optionalField "isShamanistic" bool;
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;
nameShort = translation.nameShort;
nameInWiki = translation.nameInWiki;
levels = multilingual.levels;
max = multilingual.max;
specialRules = translation.specialRules;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
primary = multilingual.primary;
aspects = multilingual.aspects;
restrictedBlessings =
multilingual.restrictedBlessings |> Ley_Option.fromOption [];
favoredCombatTechniques = multilingual.favoredCombatTechniques;
favoredSkills = multilingual.favoredSkills;
favoredSkillsSelection = multilingual.favoredSkillsSelection;
isShamanistic =
multilingual.isShamanistic |> Ley_Option.fromOption false;
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)
let primary_attribute mp =
O.Infix.(
IM.find Dynamic.is_active mp >>= fun trad ->
trad.static <&> fun trad -> trad.primary)
@@ -0,0 +1,49 @@
module Static : sig
type specialRule =
| Labeled of { label : string; text : string }
| Plain of string
type favoredCombatTechniques =
| All
| AllMelee
| AllUsedInHunting
| Specific of Id.CombatTechnique.t list
type favoredSkillsSelection = { amount : int; options : int list }
type t = {
id : int;
name : string;
nameShort : string option;
nameInWiki : string option;
levels : int option;
max : int option;
specialRules : specialRule list;
selectOptions : SelectOption.map;
input : string option;
primary : int;
aspects : (int * int) option;
restrictedBlessings : int list;
favoredCombatTechniques : favoredCombatTechniques option;
favoredSkills : int list;
favoredSkillsSelection : favoredSkillsSelection option;
isShamanistic : bool;
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
val primary_attribute : Dynamic.t Ley_IntMap.t -> int option
@@ -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,148 @@
module Shared = Activatable_Shared
module Static = struct
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aspect : int;
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;
effect : 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;
effect = json |> field "effect" Shared.Effect.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;
aspect : int;
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;
aspect = json |> field "aspect" int;
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;
effect = translation.effect;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
aspect = multilingual.aspect;
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,28 @@
module Static : sig
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aspect : int;
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,161 @@
module Shared = Activatable_Shared
module Static = struct
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Shared.Property.t;
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;
effect : string;
input : string option;
aeCost : string option;
volume : string;
bindingCost : 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;
effect = json |> field "effect" Shared.Effect.Decode.t;
input = json |> optionalField "input" Shared.Input.Decode.t;
aeCost = json |> optionalField "aeCost" Shared.AeCost.Decode.t;
volume = json |> field "volume" Shared.Volume.Decode.t;
bindingCost =
json |> optionalField "bindingCost" Shared.BindingCost.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;
property : Shared.Property.t;
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;
property = json |> field "property" Shared.Property.Decode.t;
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;
effect = translation.effect;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
aeCost = translation.aeCost;
volume = translation.volume;
bindingCost = translation.bindingCost;
property = multilingual.property;
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,31 @@
module Static : sig
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Activatable_Shared.Property.t;
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,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,169 @@
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;
isArmed : bool;
rules : string;
selectOptions : SelectOption.map;
input : string option;
penalty : string option;
advanced : Shared.AdvancedSpecialAbilities.t;
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;
isArmed : bool;
selectOptions : SelectOption.Decode.multilingual option;
advanced : Shared.AdvancedSpecialAbilities.t;
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;
isArmed = json |> field "isArmed" bool;
selectOptions =
json
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
advanced =
json |> field "advanced" Shared.AdvancedSpecialAbilities.Decode.t;
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;
isArmed = multilingual.isArmed;
rules = translation.rules;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
penalty = translation.penalty;
advanced = multilingual.advanced;
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,32 @@
module Static : sig
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
category : Activatable_Shared.CombatSpecialAbilityType.t;
isArmed : bool;
rules : string;
selectOptions : SelectOption.map;
input : string option;
penalty : string option;
advanced : Activatable_Shared.AdvancedSpecialAbilities.t;
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,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,161 @@
module Shared = Activatable_Shared
module Static = struct
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Shared.Property.t;
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;
effect : string;
input : string option;
aeCost : string option;
volume : string;
bindingCost : 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;
effect = json |> field "effect" Shared.Effect.Decode.t;
input = json |> optionalField "input" Shared.Input.Decode.t;
aeCost = json |> optionalField "aeCost" Shared.AeCost.Decode.t;
volume = json |> field "volume" Shared.Volume.Decode.t;
bindingCost =
json |> optionalField "bindingCost" Shared.BindingCost.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;
property : Shared.Property.t;
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;
property = json |> field "property" Shared.Property.Decode.t;
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;
effect = translation.effect;
selectOptions =
multilingual.selectOptions
|> Ley_Option.option SelectOption.Map.empty
(resolveSelectOptions ~src ~errata langs);
input = translation.input;
aeCost = translation.aeCost;
volume = translation.volume;
bindingCost = translation.bindingCost;
property = multilingual.property;
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,31 @@
module Static : sig
type t = {
id : int;
name : string;
nameInWiki : string option;
levels : int option;
max : int option;
effect : string;
selectOptions : SelectOption.map;
input : string option;
aeCost : string option;
volume : string;
bindingCost : string option;
property : Activatable_Shared.Property.t;
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