Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d91164ca39 | |||
| 594b3aae55 | |||
| 49caf9fa07 | |||
| 7759ea78e6 | |||
| 788be838c2 | |||
| ee3a8c454f | |||
| 908055f69c | |||
| 2194dab596 | |||
| 6eb89a518a | |||
| 9c81d5e939 | |||
| b48dded524 | |||
| 9b5a73ccf9 | |||
| ddde8957b2 | |||
| dd3caf631d | |||
| 3df0a65b2d | |||
| 785c2e26ce | |||
| 427fa14b67 | |||
| 2c13926d07 | |||
| c40b9e6fd6 | |||
| 64d72a787e | |||
| 2d0f66ae11 | |||
| 9e9622a3e2 | |||
| 0b75d1e51f | |||
| 7d9d3ed449 | |||
| 593eb4dba7 | |||
| b1373e6aaf | |||
| accc5a4ea5 | |||
| 3eb61cc55c | |||
| f7e5e06ff2 | |||
| 35048e1032 | |||
| 9ee4626f49 | |||
| 4f698af39b | |||
| 7e055864a4 | |||
| 669e2cb1b0 | |||
| b6971d190b | |||
| b0f1869886 | |||
| 74f7811f18 | |||
| 66a5329331 | |||
| dfe9e37ae9 | |||
| c41d3c4912 | |||
| 956cf3958d | |||
| 1e31e7b8de | |||
| 6776e9582a | |||
| e7e02c025c | |||
| e0b8625a4b | |||
| 7ee370c687 | |||
| ced296b47f | |||
| 832da43ecd | |||
| a62195f390 | |||
| 4da9621b3e | |||
| e15bf1784c | |||
| be7e174353 | |||
| 91f6ccbdaa | |||
| 6bee4968cf | |||
| 51b2fd85fe | |||
| 785366f25a | |||
| 2e488573f0 | |||
| a91241b759 | |||
| 13e90fe2ca | |||
| 0da83cb257 | |||
| 0fadd70dbd | |||
| cbcebc8722 | |||
| 5b2be2aba6 | |||
| fb26257254 | |||
| 6141671c11 | |||
| 8983c129be | |||
| f0fedb2ee5 | |||
| a8cc0883df | |||
| 0820860125 | |||
| 635c11cda2 | |||
| 57032d78bc | |||
| a89fd07252 | |||
| f2380eb9b4 | |||
| 3348c08808 | |||
| 4492304173 | |||
| 9acffd878e | |||
| 1667683ceb | |||
| ae44828365 | |||
| 3f48a8f42d | |||
| 5d341bb6b6 | |||
| d4f15baf96 | |||
| bfd1f05c89 | |||
| de582f9140 | |||
| f79c84caeb | |||
| 521bca34fe | |||
| d616157397 | |||
| b8cd0794ca | |||
| 7ec14231e0 | |||
| 053fcb2874 | |||
| 9b7948f6f6 | |||
| d31be41a12 | |||
| 117a732594 | |||
| 6fe2c3323f | |||
| b888851995 | |||
| 9b95187936 | |||
| 283825d6d9 | |||
| 8da9b6f641 | |||
| 2b20c8598a | |||
| 58df332c06 | |||
| b1f9a155f9 | |||
| 2bc235292c | |||
| 1eae92cabc | |||
| df6c9c310e | |||
| e653063350 |
@@ -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
@@ -1,3 +1,3 @@
|
||||
[submodule "app/Database"]
|
||||
path = app/Database
|
||||
path = src/Database
|
||||
url = https://github.com/elyukai/optolith-data.git
|
||||
|
||||
Generated
+24
@@ -0,0 +1,24 @@
|
||||
<component name="libraryTable">
|
||||
<library name="Bucklescript" type="javaScript">
|
||||
<properties>
|
||||
<sourceFilesUrls>
|
||||
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/reason-react" />
|
||||
<item url="file://$PROJECT_DIR$/node_modules/reductive" />
|
||||
</sourceFilesUrls>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/reason-react" />
|
||||
<root url="file://$PROJECT_DIR$/node_modules/reductive" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/src/Database" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,2 @@
|
||||
break-infix-before-func = true
|
||||
break-collection-expressions = wrap
|
||||
Vendored
+2
-2
@@ -14,7 +14,7 @@
|
||||
"--remote-debugging-port=9223",
|
||||
"."
|
||||
],
|
||||
"outputCapture": "std"
|
||||
"outputCapture": "std",
|
||||
},
|
||||
{
|
||||
"name": "Electron: Attach Renderer",
|
||||
@@ -33,5 +33,5 @@
|
||||
"Electron: Attach Renderer"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
Vendored
+7
-1
@@ -17,5 +17,11 @@
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.lintTask.options": "\"src/**\" -c \"./.eslintrc.yaml\" --ext=\"ts\" --ext=\"tsx\" --ignore-pattern=\"./src/App/Utilities/YAML/Schema/**\"",
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false
|
||||
"editor.detectIndentation": false,
|
||||
"[ocaml]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[rescript]": {
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+16
-59
@@ -5,67 +5,20 @@
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"type": "shell",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "npm run build",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "buildTest",
|
||||
"command": "npm run buildTest"
|
||||
},
|
||||
{
|
||||
"label": "buildMac",
|
||||
"command": "npm run buildMac"
|
||||
},
|
||||
{
|
||||
"label": "buildMacTest",
|
||||
"command": "npm run buildMacTest"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "npm run watch"
|
||||
},
|
||||
{
|
||||
"label": "bundle",
|
||||
"command": "npm run bundle",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "run",
|
||||
"command": "npm start"
|
||||
},
|
||||
{
|
||||
"label": "bundle_l10ns",
|
||||
"command": "npm run bundle_l10ns"
|
||||
},
|
||||
{
|
||||
"label": "bundle_data",
|
||||
"command": "npm run bundle_data"
|
||||
},
|
||||
{
|
||||
"label": "bundle_css",
|
||||
"command": "npm run bundle_css"
|
||||
},
|
||||
{
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"label": "re:watch",
|
||||
"type": "shell",
|
||||
"command": "npm run re:watch",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ts:watch",
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"option": "watch",
|
||||
@@ -73,12 +26,16 @@
|
||||
"problemMatcher": [
|
||||
"$tsc-watch"
|
||||
],
|
||||
"group": "build"
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "buildWin",
|
||||
"label": "watch",
|
||||
"dependsOn": ["re:watch", "ts:watch"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
-1
Submodule app/Database deleted from f2064cea3e
@@ -1,5 +0,0 @@
|
||||
const _require = require("esm")(module)
|
||||
|
||||
process.once('loaded', () => {
|
||||
global.require = _require
|
||||
})
|
||||
+25
-32
@@ -2,25 +2,25 @@
|
||||
"name": "optolith-client",
|
||||
"version": "0.1.0",
|
||||
"sources": [
|
||||
{ "dir": "deploy" },
|
||||
{ "dir": "src" },
|
||||
{ "dir": "src/App/Actions" },
|
||||
{ "dir": "src/App/Constants" },
|
||||
{ "dir": "src/App/Containers" },
|
||||
{ "dir": "src/App/Models" },
|
||||
{ "dir": "src/App/Reducers" },
|
||||
{ "dir": "src/App/Selectors" },
|
||||
{ "dir": "src/App/Utilities" },
|
||||
{ "dir": "src/App/Utilities/__tests__", "type": "dev" },
|
||||
{ "dir": "src/App/Utilities/YAML" },
|
||||
{ "dir": "src/App/Views" },
|
||||
{ "dir": "src/App/Views/Universal" },
|
||||
{ "dir": "src/Control" },
|
||||
{ "dir": "src/Data" },
|
||||
{ "dir": "src/Data/__tests__", "type": "dev" },
|
||||
{ "dir": "src/Debug" },
|
||||
{ "dir": "src/System" },
|
||||
{ "dir": "src/shims" }
|
||||
{ "dir": "src", "subdirs": [
|
||||
{ "dir": "Common" },
|
||||
{ "dir": "Dependencies" },
|
||||
{ "dir": "External" },
|
||||
{ "dir": "IO" },
|
||||
{ "dir": "Localization" },
|
||||
{ "dir": "Rated", "subdirs": [
|
||||
{ "dir": "__tests__", "type": "dev" },
|
||||
{ "dir": "ArcaneSpellworks" },
|
||||
{ "dir": "Liturgies" },
|
||||
{ "dir": "MagicalActions" }
|
||||
] },
|
||||
{ "dir": "Rules" },
|
||||
{ "dir": "Scenes" },
|
||||
{ "dir": "Sources" },
|
||||
{ "dir": "Utilities", "subdirs": [
|
||||
{ "dir": "Abstractions" }
|
||||
] }
|
||||
] }
|
||||
],
|
||||
"package-specs": {
|
||||
"module": "commonjs",
|
||||
@@ -28,24 +28,17 @@
|
||||
},
|
||||
"suffix": ".bs.js",
|
||||
"bs-dependencies": [
|
||||
"@glennsl/bs-json",
|
||||
"@glennsl/bs-jest",
|
||||
"reason-react"
|
||||
"@rescript/react",
|
||||
"bs-decoders",
|
||||
"reductive"
|
||||
],
|
||||
"reason": {
|
||||
"react-jsx": 3
|
||||
},
|
||||
"warnings": {
|
||||
"error" : "+101"
|
||||
"number": "+A-42-48",
|
||||
"error": "+A-3-4-27-32-41-44-102"
|
||||
},
|
||||
"namespace": true,
|
||||
"refmt": 3,
|
||||
"gentypeconfig": {
|
||||
"module": "commonjs",
|
||||
"language": "typescript",
|
||||
"shims": {
|
||||
"Js": "Js",
|
||||
"ReasonPervasives": "ReasonPervasives"
|
||||
}
|
||||
}
|
||||
"namespace": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Decoding
|
||||
|
||||
Here you'll find some useful information about the design of the YAML decoders.
|
||||
|
||||
## Overview
|
||||
|
||||
Decoding the database is splitted into parsing the YAML files and decoding the result. Parsing is done by the `Yaml_Parse` module and the `Yaml_Decode` module controls the decoding of the various entry types. The `Yaml` module then combines all functions into a single exported function.
|
||||
|
||||
## Decode Entry Types
|
||||
|
||||
### Decoder structure
|
||||
|
||||
All decodable main types share a common decoder layout. All values only relevant while decoding are inside a `Decode` module at the same level as the type it is the decoder for.
|
||||
|
||||
```ml
|
||||
(** Since this is the actual type, it's not really part of the decoding pipeline
|
||||
but I'll keep it here to make the definitions complete. *)
|
||||
type t = {
|
||||
(* ... *)
|
||||
}
|
||||
|
||||
module Decode = struct
|
||||
open Json.Decode
|
||||
open JsonStrict
|
||||
|
||||
type translation = { (* ... language-specific properties *) }
|
||||
|
||||
let translation json =
|
||||
{
|
||||
(* ... decode language-specific properties *)
|
||||
}
|
||||
|
||||
type multilingual = {
|
||||
(* ... language-independent properties *)
|
||||
translations : translation TranslationMap.t;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
{
|
||||
(* ... decode language-independent properties *)
|
||||
translations =
|
||||
json |> field "translations" (TranslationMap.Decode.t translation);
|
||||
}
|
||||
|
||||
let make_assoc locale_order json =
|
||||
let open Option.Infix in
|
||||
json |> multilingual |> fun multilingual ->
|
||||
multilingual.translations |> TranslationMap.preferred locale_order
|
||||
<&> fun (translation : translation) ->
|
||||
( multilingual.id,
|
||||
{
|
||||
(* ... merge language-independent and language-specific properties *)
|
||||
} )
|
||||
end
|
||||
```
|
||||
|
||||
#### Decoding types
|
||||
|
||||
Since language-specific values in the YAML files are always defined in dictionaries where the keys map to the locale identifiers, we always split the language-specific and the language-independent parts.
|
||||
|
||||
The language-specific part of a type is always defined as type `translation`. The corresponding `translation` decoder function is later used to decode the translation dictionary, also called *translation map*.
|
||||
|
||||
The language-independent part of a type is defined as type `multilingual` and usually has the property `translations`, which is the dictionary of all translations. The corresponding `multilingual` decoder function uses the `translation` decoder together with the translation map decoder from the `TranslationMap` module to fully decode the dictionary of translations.
|
||||
|
||||
Using a list of preferred locales, sorted by preference, (`Locale.Order.t`) we can retrieve the most preferred translation using `TranslationMap.preferred` and then combine the translation values and the multilingual values to form the final record type. Following OCaml conventions, this function is called `make`.
|
||||
|
||||
Often, in order to be used more easily, the `make` function returns a pair of the respective entry's identifier and the entry itself to create a `Map` more easily. To hint at this specific use, the function is then called `make_assoc`. If a nested type is only decoded in a context of collection type (like a list), this decoder can be provided using a `make` variant as well (in this case, `make_list`).
|
||||
|
||||
If other decodable types are nested withing a type, the `multilingual` function usually takes the list of preferred locales as well to be able to pass it down to the nested types' decoders.
|
||||
|
||||
### `Decode` module visibility conventions
|
||||
|
||||
Only the `make` function or its variant should be exposed.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- The language-specific tyüe is always named `translation`.
|
||||
- The record containing all language-independent values as well as the translation map is called `multilingual` and the translation map is at the property `translations` (just like in the source).
|
||||
- A decoder for a specific type has the same name as the type.
|
||||
- The function resolving the translation map and returning the actual main type is called `make`.
|
||||
- A potential helper function that decodes the type as part of a collection is called `make_c`, where `c` is the name of the collection, e.g. `make_list`.
|
||||
@@ -0,0 +1,8 @@
|
||||
# *Dynamic* and *Static*
|
||||
|
||||
In entry type definition files, you'll often see the content is wrapped in a `Dynamic` and a `Static` module. What does that mean?
|
||||
|
||||
- **Static** values come from the YAML files, so they have been defined there and they will not change while the program runs.
|
||||
- **Dynamic** values are kind of like *instances* of the *static* values. They are part of the heroes and define how an entry is configured for that hero.
|
||||
|
||||
For example, a `Spell.Static.t` type is the static representation of a spell: It contains it's name, IC, duration, target category, and so on. The `Spell.Dynamic.t` type is the dynamic representation of the spell for a specific hero: Is the spell active? If yes, which SR does it have?
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generating module code
|
||||
|
||||
## Convert syntax
|
||||
|
||||
Using `bsrefmt`, you can convert between syntaxes:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsrefmt path/to/file --print=syntax
|
||||
```
|
||||
|
||||
`syntax` can be `res`, `re` and `ml`. The converted code from the referenced file will be output to command line.
|
||||
|
||||
## Interface files
|
||||
|
||||
Using `bsc`, you can generate an interface file in OCaml syntax if only the implementation file is present:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
To generate in Reason syntax, provide the `-bs-re-out` flag.
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc -bs-re-out ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
The generated code will be output to command line.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Labelled arguments syntax in OCaml
|
||||
|
||||
The syntax for labelled arguments might not always be obvious. Below you'll find a cheatsheet for the syntax in both implementations and signatures.
|
||||
|
||||
## Basic
|
||||
|
||||
### Definition
|
||||
|
||||
```ocaml
|
||||
let f ~param:param = param
|
||||
```
|
||||
|
||||
You can omit the variable name after the label itself if its the same as the label.
|
||||
|
||||
```ocaml
|
||||
let f ~param = param
|
||||
```
|
||||
|
||||
### Call
|
||||
|
||||
```ocaml
|
||||
let x = f ~param:value
|
||||
```
|
||||
|
||||
### Signature
|
||||
|
||||
```ocaml
|
||||
val f : ~param:'a -> 'a
|
||||
```
|
||||
|
||||
## Optional
|
||||
|
||||
```ocaml
|
||||
let f ?param:param () = param
|
||||
let f ?param () = param
|
||||
|
||||
val f : ?param:'a -> unit -> 'a
|
||||
```
|
||||
|
||||
You can also provide a default value.
|
||||
|
||||
```ocaml
|
||||
let f ?(param=0) () = param
|
||||
let f ?param:(param=0) () = param
|
||||
```
|
||||
|
||||
A function defining optional arguments must also define at least one non-labelled argument. A typical parameter for this use case is unit.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ~param:0 () (* x = Some 0 *)
|
||||
```
|
||||
|
||||
You may also forward an optional type to an optional argument.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ?param:None () (* x = None *)
|
||||
let x = f ?param:(Some 0) () (* x = Some 0 *)
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
# Structure
|
||||
|
||||
- `src` – The source code main folder, containing program entry points.
|
||||
- `Common` – Types and functions associated with multiple ‘namespaces’.
|
||||
- `Database` – The flat-file database containing all static data from the source books.
|
||||
- `Dependencies` – Types and functions for working with dependencies between entries and the prerequisites they are based on.
|
||||
- `External` – Bindings to external libraries via FFI.
|
||||
- `Localization` – Types and functions related to different languages in the app and the source books.
|
||||
- `Rated` – All data types for entries with *ratings* (attributes, skills or combat techniques) and related types and logic.
|
||||
- `ArcaneSpellworks` – All data types for arcane spellworks (spells and rituals) and related types and logic.
|
||||
- `Liturgies` – All data types for liturgies (liturgical chants and ceremonies) and related types and logic.
|
||||
- `MagicalActions` – All data types for magical actions (curses, Elven magical songs, …) and related types and logic.
|
||||
- `Rules` – All data types for rule entries.
|
||||
- `Sources` – Types and functions dealing with data related to source books: Source books themselves, references, availability by book and errata.
|
||||
- `Utilities` – Utility functions and types for working with standard types or I/O.
|
||||
- `Abstractions` – Utility functions and types for working with type abstractions such as monads that can be used to create similar functions for different container types.
|
||||
+2
-13
@@ -1,25 +1,14 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @typedef {import('ts-jest')}
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
transform: {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
||||
testRegex: "(/__tests__/.*|(\\.|/)(test|spec)|_(test|spec)\\.bs)\\.jsx?$",
|
||||
moduleFileExtensions: [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
testEnvironment: "node",
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsConfig: "tsconfig.test.json"
|
||||
}
|
||||
}
|
||||
testEnvironment: "node"
|
||||
}
|
||||
|
||||
Generated
+17693
-5148
File diff suppressed because it is too large
Load Diff
+55
-66
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "optolith",
|
||||
"productName": "Optolith",
|
||||
"productName": "Optolith Insider",
|
||||
"version": "1.4.3-alpha.0",
|
||||
"description": "A generator for heroes for the P&P-RPG 'The Dark Eye'.",
|
||||
"author": {
|
||||
@@ -9,73 +9,62 @@
|
||||
},
|
||||
"homepage": "https://github.com/elyukai/optolith-client",
|
||||
"license": "MPL-2.0",
|
||||
"main": "app/main.js",
|
||||
"main": "src/Main.bs.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",
|
||||
"@rescript/react": "^0.10.3",
|
||||
"bs-decoders": "^0.6.0",
|
||||
"electron": "^13.1.7",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-log": "^4.2.4",
|
||||
"electron-updater": "^4.3.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"moment": "^2.25.3",
|
||||
"mousetrap": "^1.6.5",
|
||||
"react": "^17.0.2",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-markdown": "^6.0.2",
|
||||
"react-redux": "^7.2.1",
|
||||
"react-textfit": "^1.1.1",
|
||||
"reason-react": "^0.9.1",
|
||||
"reductive": "^2.1.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"remark-breaks": "^1.0.5",
|
||||
"rescript": "^9.1.4",
|
||||
"reselect": "^4.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"ssh2-sftp-client": "^5.3.1",
|
||||
"yaml": "^1.10.2"
|
||||
},
|
||||
"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",
|
||||
"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",
|
||||
"@commitlint/cli": "^12.1.1",
|
||||
"@commitlint/config-conventional": "^12.1.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/mousetrap": "^1.6.7",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-portal": "^4.0.2",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/semver": "^7.3.4",
|
||||
"electron-builder": "^22.10.5",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.3",
|
||||
"qusly-core": "2.5.2",
|
||||
"sass": "1.29.0",
|
||||
"standard-version": "9.0.0",
|
||||
"ts-jest": "26.4.4",
|
||||
"ts-loader": "8.0.11",
|
||||
"typescript": "3.8.3",
|
||||
"webpack": "5.4.0",
|
||||
"webpack-cli": "4.2.0"
|
||||
"sass": "^1.32.11",
|
||||
"standard-version": "^9.2.0",
|
||||
"webpack": "^5.35.1",
|
||||
"webpack-cli": "^4.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prerelease:win": "set NODE_ENV=development && node ./deploy/buildInsiderWin.js",
|
||||
@@ -84,8 +73,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)
|
||||
@@ -0,0 +1,199 @@
|
||||
type action =
|
||||
| AddAttributePoint(int)
|
||||
| RemoveAttributePoint(int)
|
||||
| AddLifePoint
|
||||
| RemoveLifePoint
|
||||
| AddArcaneEnergyPoint
|
||||
| RemoveArcaneEnergyPoint
|
||||
| AddKarmaPoint
|
||||
| RemoveKarmaPoint
|
||||
| AddLostLifePoint
|
||||
| RemoveLostLifePoint
|
||||
| AddLostLifePoints(int)
|
||||
| AddBoughtBackArcaneEnergyPoint
|
||||
| RemoveBoughtBackArcaneEnergyPoint
|
||||
| AddLostArcaneEnergyPoint
|
||||
| RemoveLostArcaneEnergyPoint
|
||||
| AddLostArcaneEnergyPoints(int)
|
||||
| AddBoughtBackKarmaPoint
|
||||
| RemoveBoughtBackKarmaPoint
|
||||
| AddLostKarmaPoint
|
||||
| RemoveLostKarmaPoint
|
||||
| AddLostKarmaPoints(int)
|
||||
| SetAttributeAdjustment(int);
|
||||
|
||||
// export const addAttributePoint = (id: string): ReduxAction<Promise<void>> =>
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const wiki_attributes = getWikiAttributes (state)
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
// const mhero_attributes = fmapF (mhero) (HeroModel.A.attributes)
|
||||
//
|
||||
// const missingAPForInc =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (getAreSufficientAPAvailableForIncrease (getIsInCharacterCreation (state))
|
||||
// (bind (mhero_attributes)
|
||||
// (lookup (id))))
|
||||
// (lookup (id) (wiki_attributes)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAPForInc)) {
|
||||
// dispatch<AddAttributePointAction> ({
|
||||
// type: ActionTypes.ADD_ATTRIBUTE_POINT,
|
||||
// payload: {
|
||||
// id,
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAPForInc)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addLifePoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedLifePoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddLifePointAction> ({
|
||||
// type: ActionTypes.ADD_LIFE_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addArcaneEnergyPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedArcaneEnergyPoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddArcaneEnergyPointAction> ({
|
||||
// type: ActionTypes.ADD_ARCANE_ENERGY_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addKarmaPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// liftM2 (pipe (
|
||||
//
|
||||
// // get AP for added points
|
||||
// curryN (getAPForInc) ("D"),
|
||||
//
|
||||
// // AP are passed to result and result finally gets the available AP
|
||||
// getMissingAP (getIsInCharacterCreation (state))
|
||||
// ))
|
||||
// (getAddedKarmaPoints (state)),
|
||||
// join
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddKarmaPointAction> ({
|
||||
// type: ActionTypes.ADD_KARMA_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addBoughtBackAEPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// bindF (getMissingAP (getIsInCharacterCreation (state))
|
||||
// (2))
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddBoughtBackAEPointAction> ({
|
||||
// type: ActionTypes.ADD_BOUGHT_BACK_AE_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export const addBoughtBackKPPoint: ReduxAction<Promise<void>> =
|
||||
// async (dispatch, getState) => {
|
||||
// const state = getState ()
|
||||
// const mhero = getCurrentHeroPresent (state)
|
||||
//
|
||||
// const missingAP =
|
||||
// pipe_ (
|
||||
// mhero,
|
||||
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
|
||||
// join,
|
||||
// bindF (getMissingAP (getIsInCharacterCreation (state))
|
||||
// (2))
|
||||
// )
|
||||
//
|
||||
// if (isNothing (missingAP)) {
|
||||
// dispatch<AddBoughtBackKPPointAction> ({
|
||||
// type: ActionTypes.ADD_BOUGHT_BACK_KP_POINT,
|
||||
// })
|
||||
// }
|
||||
// else {
|
||||
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,5 @@
|
||||
type action =
|
||||
| SetCulture(int)
|
||||
| SetCulturesSortOrder(FilterOptions.culturesSortOptions)
|
||||
| SetCulturesVisibilityFilter(FilterOptions.culturesVisibilityFilter)
|
||||
| SetCulturesFilterText(string);
|
||||
@@ -0,0 +1,50 @@
|
||||
type t =
|
||||
| Advantage of Advantage.Static.t
|
||||
| Disadvantage of Disadvantage.Static.t
|
||||
| GeneralSpecialAbility of GeneralSpecialAbility.Static.t
|
||||
| FatePointSpecialAbility of FatePointSpecialAbility.Static.t
|
||||
| CombatSpecialAbility of CombatSpecialAbility.Static.t
|
||||
| MagicalSpecialAbility of MagicalSpecialAbility.Static.t
|
||||
| StaffEnchantment of StaffEnchantment.Static.t
|
||||
| FamiliarSpecialAbility of FamiliarSpecialAbility.Static.t
|
||||
| KarmaSpecialAbility of KarmaSpecialAbility.Static.t
|
||||
| ProtectiveWardingCircleSpecialAbility of
|
||||
ProtectiveWardingCircleSpecialAbility.Static.t
|
||||
| CombatStyleSpecialAbility of CombatStyleSpecialAbility.Static.t
|
||||
| AdvancedCombatSpecialAbility of AdvancedCombatSpecialAbility.Static.t
|
||||
| CommandSpecialAbility of CommandSpecialAbility.Static.t
|
||||
| MagicStyleSpecialAbility of MagicStyleSpecialAbility.Static.t
|
||||
| AdvancedMagicalSpecialAbility of AdvancedMagicalSpecialAbility.Static.t
|
||||
| SpellSwordEnchantment of SpellSwordEnchantment.Static.t
|
||||
| DaggerRitual of DaggerRitual.Static.t
|
||||
| InstrumentEnchantment of InstrumentEnchantment.Static.t
|
||||
| AttireEnchantment of AttireEnchantment.Static.t
|
||||
| OrbEnchantment of OrbEnchantment.Static.t
|
||||
| WandEnchantment of WandEnchantment.Static.t
|
||||
| BrawlingSpecialAbility of BrawlingSpecialAbility.Static.t
|
||||
| AncestorGlyph of AncestorGlyph.Static.t
|
||||
| CeremonialItemSpecialAbility of CeremonialItemSpecialAbility.Static.t
|
||||
| Sermon of Sermon.Static.t
|
||||
| LiturgicalStyleSpecialAbility of LiturgicalStyleSpecialAbility.Static.t
|
||||
| AdvancedKarmaSpecialAbility of AdvancedKarmaSpecialAbility.Static.t
|
||||
| Vision of Vision.Static.t
|
||||
| MagicalTradition of MagicalTradition.Static.t
|
||||
| BlessedTradition of BlessedTradition.Static.t
|
||||
| Paktgeschenk of Paktgeschenk.Static.t
|
||||
| SikaryanRaubSonderfertigkeit of SikaryanRaubSonderfertigkeit.Static.t
|
||||
| LykanthropischeGabe of LykanthropischeGabe.Static.t
|
||||
| Talentstilsonderfertigkeit of Talentstilsonderfertigkeit.Static.t
|
||||
| ErweiterteTalentsonderfertigkeit of
|
||||
ErweiterteTalentsonderfertigkeit.Static.t
|
||||
| Kugelzauber of Kugelzauber.Static.t
|
||||
| Kesselzauber of Kesselzauber.Static.t
|
||||
| Kappenzauber of Kappenzauber.Static.t
|
||||
| Spielzeugzauber of Spielzeugzauber.Static.t
|
||||
| Schalenzauber of Schalenzauber.Static.t
|
||||
| SexSchicksalspunkteSonderfertigkeit of
|
||||
SexSchicksalspunkteSonderfertigkeit.Static.t
|
||||
| SexSonderfertigkeit of SexSonderfertigkeit.Static.t
|
||||
| Waffenzauber of Waffenzauber.Static.t
|
||||
| Sichelritual of Sichelritual.Static.t
|
||||
| Ringzauber of Ringzauber.Static.t
|
||||
| Chronikzauber of Chronikzauber.Static.t
|
||||
@@ -0,0 +1,349 @@
|
||||
let isActive (x : 'a Activatable_Dynamic.t) = Ley_List.Extra.notNull x.active
|
||||
|
||||
let isActiveM x = Ley_Option.option false isActive x
|
||||
|
||||
let id = function
|
||||
| Activatable.Advantage x -> Id.Activatable.Advantage x.id
|
||||
| Disadvantage x -> Disadvantage x.id
|
||||
| GeneralSpecialAbility x -> GeneralSpecialAbility x.id
|
||||
| FatePointSpecialAbility x -> FatePointSpecialAbility x.id
|
||||
| CombatSpecialAbility x -> CombatSpecialAbility x.id
|
||||
| MagicalSpecialAbility x -> MagicalSpecialAbility x.id
|
||||
| StaffEnchantment x -> StaffEnchantment x.id
|
||||
| FamiliarSpecialAbility x -> FamiliarSpecialAbility x.id
|
||||
| KarmaSpecialAbility x -> KarmaSpecialAbility x.id
|
||||
| ProtectiveWardingCircleSpecialAbility x ->
|
||||
ProtectiveWardingCircleSpecialAbility x.id
|
||||
| CombatStyleSpecialAbility x -> CombatStyleSpecialAbility x.id
|
||||
| AdvancedCombatSpecialAbility x -> AdvancedCombatSpecialAbility x.id
|
||||
| CommandSpecialAbility x -> CommandSpecialAbility x.id
|
||||
| MagicStyleSpecialAbility x -> MagicStyleSpecialAbility x.id
|
||||
| AdvancedMagicalSpecialAbility x -> AdvancedMagicalSpecialAbility x.id
|
||||
| SpellSwordEnchantment x -> SpellSwordEnchantment x.id
|
||||
| DaggerRitual x -> DaggerRitual x.id
|
||||
| InstrumentEnchantment x -> InstrumentEnchantment x.id
|
||||
| AttireEnchantment x -> AttireEnchantment x.id
|
||||
| OrbEnchantment x -> OrbEnchantment x.id
|
||||
| WandEnchantment x -> WandEnchantment x.id
|
||||
| BrawlingSpecialAbility x -> BrawlingSpecialAbility x.id
|
||||
| AncestorGlyph x -> AncestorGlyph x.id
|
||||
| CeremonialItemSpecialAbility x -> CeremonialItemSpecialAbility x.id
|
||||
| Sermon x -> Sermon x.id
|
||||
| LiturgicalStyleSpecialAbility x -> LiturgicalStyleSpecialAbility x.id
|
||||
| AdvancedKarmaSpecialAbility x -> AdvancedKarmaSpecialAbility x.id
|
||||
| Vision x -> Vision x.id
|
||||
| MagicalTradition x -> MagicalTradition x.id
|
||||
| BlessedTradition x -> BlessedTradition x.id
|
||||
| Paktgeschenk x -> Paktgeschenk x.id
|
||||
| SikaryanRaubSonderfertigkeit x -> SikaryanRaubSonderfertigkeit x.id
|
||||
| LykanthropischeGabe x -> LykanthropischeGabe x.id
|
||||
| Talentstilsonderfertigkeit x -> Talentstilsonderfertigkeit x.id
|
||||
| ErweiterteTalentsonderfertigkeit x -> ErweiterteTalentsonderfertigkeit x.id
|
||||
| Kugelzauber x -> Kugelzauber x.id
|
||||
| Kesselzauber x -> Kesselzauber x.id
|
||||
| Kappenzauber x -> Kappenzauber x.id
|
||||
| Spielzeugzauber x -> Spielzeugzauber x.id
|
||||
| Schalenzauber x -> Schalenzauber x.id
|
||||
| SexSchicksalspunkteSonderfertigkeit x ->
|
||||
SexSchicksalspunkteSonderfertigkeit x.id
|
||||
| SexSonderfertigkeit x -> SexSonderfertigkeit x.id
|
||||
| Waffenzauber x -> Waffenzauber x.id
|
||||
| Sichelritual x -> Sichelritual x.id
|
||||
| Ringzauber x -> Ringzauber x.id
|
||||
| Chronikzauber x -> Chronikzauber x.id
|
||||
|
||||
let id' = function
|
||||
| Activatable.Advantage x -> x.id
|
||||
| Disadvantage x -> x.id
|
||||
| GeneralSpecialAbility x -> x.id
|
||||
| FatePointSpecialAbility x -> x.id
|
||||
| CombatSpecialAbility x -> x.id
|
||||
| MagicalSpecialAbility x -> x.id
|
||||
| StaffEnchantment x -> x.id
|
||||
| FamiliarSpecialAbility x -> x.id
|
||||
| KarmaSpecialAbility x -> x.id
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.id
|
||||
| CombatStyleSpecialAbility x -> x.id
|
||||
| AdvancedCombatSpecialAbility x -> x.id
|
||||
| CommandSpecialAbility x -> x.id
|
||||
| MagicStyleSpecialAbility x -> x.id
|
||||
| AdvancedMagicalSpecialAbility x -> x.id
|
||||
| SpellSwordEnchantment x -> x.id
|
||||
| DaggerRitual x -> x.id
|
||||
| InstrumentEnchantment x -> x.id
|
||||
| AttireEnchantment x -> x.id
|
||||
| OrbEnchantment x -> x.id
|
||||
| WandEnchantment x -> x.id
|
||||
| BrawlingSpecialAbility x -> x.id
|
||||
| AncestorGlyph x -> x.id
|
||||
| CeremonialItemSpecialAbility x -> x.id
|
||||
| Sermon x -> x.id
|
||||
| LiturgicalStyleSpecialAbility x -> x.id
|
||||
| AdvancedKarmaSpecialAbility x -> x.id
|
||||
| Vision x -> x.id
|
||||
| MagicalTradition x -> x.id
|
||||
| BlessedTradition x -> x.id
|
||||
| Paktgeschenk x -> x.id
|
||||
| SikaryanRaubSonderfertigkeit x -> x.id
|
||||
| LykanthropischeGabe x -> x.id
|
||||
| Talentstilsonderfertigkeit x -> x.id
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.id
|
||||
| Kugelzauber x -> x.id
|
||||
| Kesselzauber x -> x.id
|
||||
| Kappenzauber x -> x.id
|
||||
| Spielzeugzauber x -> x.id
|
||||
| Schalenzauber x -> x.id
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.id
|
||||
| SexSonderfertigkeit x -> x.id
|
||||
| Waffenzauber x -> x.id
|
||||
| Sichelritual x -> x.id
|
||||
| Ringzauber x -> x.id
|
||||
| Chronikzauber x -> x.id
|
||||
|
||||
let idDeepVariant x = x |> id |> Id.Activatable.to_nested
|
||||
|
||||
let name = function
|
||||
| Activatable.Advantage x -> x.name
|
||||
| Disadvantage x -> x.name
|
||||
| GeneralSpecialAbility x -> x.name
|
||||
| FatePointSpecialAbility x -> x.name
|
||||
| CombatSpecialAbility x -> x.name
|
||||
| MagicalSpecialAbility x -> x.name
|
||||
| StaffEnchantment x -> x.name
|
||||
| FamiliarSpecialAbility x -> x.name
|
||||
| KarmaSpecialAbility x -> x.name
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.name
|
||||
| CombatStyleSpecialAbility x -> x.name
|
||||
| AdvancedCombatSpecialAbility x -> x.name
|
||||
| CommandSpecialAbility x -> x.name
|
||||
| MagicStyleSpecialAbility x -> x.name
|
||||
| AdvancedMagicalSpecialAbility x -> x.name
|
||||
| SpellSwordEnchantment x -> x.name
|
||||
| DaggerRitual x -> x.name
|
||||
| InstrumentEnchantment x -> x.name
|
||||
| AttireEnchantment x -> x.name
|
||||
| OrbEnchantment x -> x.name
|
||||
| WandEnchantment x -> x.name
|
||||
| BrawlingSpecialAbility x -> x.name
|
||||
| AncestorGlyph x -> x.name
|
||||
| CeremonialItemSpecialAbility x -> x.name
|
||||
| Sermon x -> x.name
|
||||
| LiturgicalStyleSpecialAbility x -> x.name
|
||||
| AdvancedKarmaSpecialAbility x -> x.name
|
||||
| Vision x -> x.name
|
||||
| MagicalTradition x -> x.name
|
||||
| BlessedTradition x -> x.name
|
||||
| Paktgeschenk x -> x.name
|
||||
| SikaryanRaubSonderfertigkeit x -> x.name
|
||||
| LykanthropischeGabe x -> x.name
|
||||
| Talentstilsonderfertigkeit x -> x.name
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.name
|
||||
| Kugelzauber x -> x.name
|
||||
| Kesselzauber x -> x.name
|
||||
| Kappenzauber x -> x.name
|
||||
| Spielzeugzauber x -> x.name
|
||||
| Schalenzauber x -> x.name
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.name
|
||||
| SexSonderfertigkeit x -> x.name
|
||||
| Waffenzauber x -> x.name
|
||||
| Sichelritual x -> x.name
|
||||
| Ringzauber x -> x.name
|
||||
| Chronikzauber x -> x.name
|
||||
|
||||
let selectOptions = function
|
||||
| Activatable.Advantage x -> x.selectOptions
|
||||
| Disadvantage x -> x.selectOptions
|
||||
| GeneralSpecialAbility x -> x.selectOptions
|
||||
| FatePointSpecialAbility x -> x.selectOptions
|
||||
| CombatSpecialAbility x -> x.selectOptions
|
||||
| MagicalSpecialAbility x -> x.selectOptions
|
||||
| StaffEnchantment x -> x.selectOptions
|
||||
| FamiliarSpecialAbility x -> x.selectOptions
|
||||
| KarmaSpecialAbility x -> x.selectOptions
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.selectOptions
|
||||
| CombatStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedCombatSpecialAbility x -> x.selectOptions
|
||||
| CommandSpecialAbility x -> x.selectOptions
|
||||
| MagicStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedMagicalSpecialAbility x -> x.selectOptions
|
||||
| SpellSwordEnchantment x -> x.selectOptions
|
||||
| DaggerRitual x -> x.selectOptions
|
||||
| InstrumentEnchantment x -> x.selectOptions
|
||||
| AttireEnchantment x -> x.selectOptions
|
||||
| OrbEnchantment x -> x.selectOptions
|
||||
| WandEnchantment x -> x.selectOptions
|
||||
| BrawlingSpecialAbility x -> x.selectOptions
|
||||
| AncestorGlyph x -> x.selectOptions
|
||||
| CeremonialItemSpecialAbility x -> x.selectOptions
|
||||
| Sermon x -> x.selectOptions
|
||||
| LiturgicalStyleSpecialAbility x -> x.selectOptions
|
||||
| AdvancedKarmaSpecialAbility x -> x.selectOptions
|
||||
| Vision x -> x.selectOptions
|
||||
| MagicalTradition x -> x.selectOptions
|
||||
| BlessedTradition x -> x.selectOptions
|
||||
| Paktgeschenk x -> x.selectOptions
|
||||
| SikaryanRaubSonderfertigkeit x -> x.selectOptions
|
||||
| LykanthropischeGabe x -> x.selectOptions
|
||||
| Talentstilsonderfertigkeit x -> x.selectOptions
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.selectOptions
|
||||
| Kugelzauber x -> x.selectOptions
|
||||
| Kesselzauber x -> x.selectOptions
|
||||
| Kappenzauber x -> x.selectOptions
|
||||
| Spielzeugzauber x -> x.selectOptions
|
||||
| Schalenzauber x -> x.selectOptions
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.selectOptions
|
||||
| SexSonderfertigkeit x -> x.selectOptions
|
||||
| Waffenzauber x -> x.selectOptions
|
||||
| Sichelritual x -> x.selectOptions
|
||||
| Ringzauber x -> x.selectOptions
|
||||
| Chronikzauber x -> x.selectOptions
|
||||
|
||||
let input = function
|
||||
| Activatable.Advantage x -> x.input
|
||||
| Disadvantage x -> x.input
|
||||
| GeneralSpecialAbility x -> x.input
|
||||
| FatePointSpecialAbility x -> x.input
|
||||
| CombatSpecialAbility x -> x.input
|
||||
| MagicalSpecialAbility x -> x.input
|
||||
| StaffEnchantment x -> x.input
|
||||
| FamiliarSpecialAbility x -> x.input
|
||||
| KarmaSpecialAbility x -> x.input
|
||||
| ProtectiveWardingCircleSpecialAbility _ -> None
|
||||
| CombatStyleSpecialAbility x -> x.input
|
||||
| AdvancedCombatSpecialAbility x -> x.input
|
||||
| CommandSpecialAbility x -> x.input
|
||||
| MagicStyleSpecialAbility x -> x.input
|
||||
| AdvancedMagicalSpecialAbility x -> x.input
|
||||
| SpellSwordEnchantment x -> x.input
|
||||
| DaggerRitual x -> x.input
|
||||
| InstrumentEnchantment x -> x.input
|
||||
| AttireEnchantment x -> x.input
|
||||
| OrbEnchantment x -> x.input
|
||||
| WandEnchantment x -> x.input
|
||||
| BrawlingSpecialAbility x -> x.input
|
||||
| AncestorGlyph x -> x.input
|
||||
| CeremonialItemSpecialAbility x -> x.input
|
||||
| Sermon x -> x.input
|
||||
| LiturgicalStyleSpecialAbility x -> x.input
|
||||
| AdvancedKarmaSpecialAbility x -> x.input
|
||||
| Vision x -> x.input
|
||||
| MagicalTradition x -> x.input
|
||||
| BlessedTradition x -> x.input
|
||||
| Paktgeschenk x -> x.input
|
||||
| SikaryanRaubSonderfertigkeit x -> x.input
|
||||
| LykanthropischeGabe x -> x.input
|
||||
| Talentstilsonderfertigkeit x -> x.input
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.input
|
||||
| Kugelzauber x -> x.input
|
||||
| Kesselzauber x -> x.input
|
||||
| Kappenzauber x -> x.input
|
||||
| Spielzeugzauber x -> x.input
|
||||
| Schalenzauber x -> x.input
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.input
|
||||
| SexSonderfertigkeit x -> x.input
|
||||
| Waffenzauber x -> x.input
|
||||
| Sichelritual x -> x.input
|
||||
| Ringzauber x -> x.input
|
||||
| Chronikzauber x -> x.input
|
||||
|
||||
let apValue = function
|
||||
| Activatable.Advantage x -> x.apValue
|
||||
| Disadvantage x -> x.apValue
|
||||
| GeneralSpecialAbility x -> x.apValue
|
||||
| FatePointSpecialAbility x -> x.apValue
|
||||
| CombatSpecialAbility x -> x.apValue
|
||||
| MagicalSpecialAbility x -> x.apValue
|
||||
| StaffEnchantment x -> x.apValue
|
||||
| FamiliarSpecialAbility x -> x.apValue
|
||||
| KarmaSpecialAbility x -> x.apValue
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.apValue
|
||||
| CombatStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedCombatSpecialAbility x -> x.apValue
|
||||
| CommandSpecialAbility x -> x.apValue
|
||||
| MagicStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedMagicalSpecialAbility x -> x.apValue
|
||||
| SpellSwordEnchantment x -> x.apValue
|
||||
| DaggerRitual x -> x.apValue
|
||||
| InstrumentEnchantment x -> x.apValue
|
||||
| AttireEnchantment x -> x.apValue
|
||||
| OrbEnchantment x -> x.apValue
|
||||
| WandEnchantment x -> x.apValue
|
||||
| BrawlingSpecialAbility x -> x.apValue
|
||||
| AncestorGlyph x -> x.apValue
|
||||
| CeremonialItemSpecialAbility x -> x.apValue
|
||||
| Sermon x -> x.apValue
|
||||
| LiturgicalStyleSpecialAbility x -> x.apValue
|
||||
| AdvancedKarmaSpecialAbility x -> x.apValue
|
||||
| Vision x -> x.apValue
|
||||
| MagicalTradition x -> x.apValue
|
||||
| BlessedTradition x -> x.apValue
|
||||
| Paktgeschenk x -> x.apValue
|
||||
| SikaryanRaubSonderfertigkeit x -> x.apValue
|
||||
| LykanthropischeGabe x -> x.apValue
|
||||
| Talentstilsonderfertigkeit x -> x.apValue
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.apValue
|
||||
| Kugelzauber x -> x.apValue
|
||||
| Kesselzauber x -> x.apValue
|
||||
| Kappenzauber x -> x.apValue
|
||||
| Spielzeugzauber x -> x.apValue
|
||||
| Schalenzauber x -> x.apValue
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.apValue
|
||||
| SexSonderfertigkeit x -> x.apValue
|
||||
| Waffenzauber x -> x.apValue
|
||||
| Sichelritual x -> x.apValue
|
||||
| Ringzauber x -> x.apValue
|
||||
| Chronikzauber x -> x.apValue
|
||||
|
||||
let apValue' x =
|
||||
x |> apValue |> function
|
||||
| Some (Activatable_Shared.ApValue.Flat x) -> Some (OneOrMany.One x)
|
||||
| Some (PerLevel xs) -> Some (Many xs)
|
||||
| Some Option | None -> None
|
||||
|
||||
let max = function
|
||||
| Activatable.Advantage x -> x.max
|
||||
| Disadvantage x -> x.max
|
||||
| GeneralSpecialAbility x -> x.max
|
||||
| FatePointSpecialAbility x -> x.max
|
||||
| CombatSpecialAbility x -> x.max
|
||||
| MagicalSpecialAbility x -> x.max
|
||||
| StaffEnchantment x -> x.max
|
||||
| FamiliarSpecialAbility x -> x.max
|
||||
| KarmaSpecialAbility x -> x.max
|
||||
| ProtectiveWardingCircleSpecialAbility x -> x.max
|
||||
| CombatStyleSpecialAbility x -> x.max
|
||||
| AdvancedCombatSpecialAbility x -> x.max
|
||||
| CommandSpecialAbility x -> x.max
|
||||
| MagicStyleSpecialAbility x -> x.max
|
||||
| AdvancedMagicalSpecialAbility x -> x.max
|
||||
| SpellSwordEnchantment x -> x.max
|
||||
| DaggerRitual x -> x.max
|
||||
| InstrumentEnchantment x -> x.max
|
||||
| AttireEnchantment x -> x.max
|
||||
| OrbEnchantment x -> x.max
|
||||
| WandEnchantment x -> x.max
|
||||
| BrawlingSpecialAbility x -> x.max
|
||||
| AncestorGlyph x -> x.max
|
||||
| CeremonialItemSpecialAbility x -> x.max
|
||||
| Sermon x -> x.max
|
||||
| LiturgicalStyleSpecialAbility x -> x.max
|
||||
| AdvancedKarmaSpecialAbility x -> x.max
|
||||
| Vision x -> x.max
|
||||
| MagicalTradition x -> x.max
|
||||
| BlessedTradition x -> x.max
|
||||
| Paktgeschenk x -> x.max
|
||||
| SikaryanRaubSonderfertigkeit x -> x.max
|
||||
| LykanthropischeGabe x -> x.max
|
||||
| Talentstilsonderfertigkeit x -> x.max
|
||||
| ErweiterteTalentsonderfertigkeit x -> x.max
|
||||
| Kugelzauber x -> x.max
|
||||
| Kesselzauber x -> x.max
|
||||
| Kappenzauber x -> x.max
|
||||
| Spielzeugzauber x -> x.max
|
||||
| Schalenzauber x -> x.max
|
||||
| SexSchicksalspunkteSonderfertigkeit x -> x.max
|
||||
| SexSonderfertigkeit x -> x.max
|
||||
| Waffenzauber x -> x.max
|
||||
| Sichelritual x -> x.max
|
||||
| Ringzauber x -> x.max
|
||||
| Chronikzauber x -> x.max
|
||||
@@ -0,0 +1,37 @@
|
||||
(**
|
||||
* Convenient accessor functions for static Activatable entries.
|
||||
*
|
||||
* This module provides some accessor functions for accessing properties of a
|
||||
* static Activatable entry. The functions are named so that the name equals the
|
||||
* property name.
|
||||
*
|
||||
* It also provides a few functions for simple derived values.
|
||||
*)
|
||||
|
||||
val isActive : 'a Activatable_Dynamic.t -> bool
|
||||
(**
|
||||
* Is an Activatable entry active?
|
||||
*)
|
||||
|
||||
val isActiveM : 'a Activatable_Dynamic.t option -> bool
|
||||
(**
|
||||
* Is an Activatable, where the entry may not have been created, active?
|
||||
*)
|
||||
|
||||
val id : Activatable.t -> Id.Activatable.t
|
||||
|
||||
val id' : Activatable.t -> int
|
||||
|
||||
val idDeepVariant : Activatable.t -> Id.Activatable.Nested.t
|
||||
|
||||
val name : Activatable.t -> string
|
||||
|
||||
val selectOptions : Activatable.t -> SelectOption.map
|
||||
|
||||
val input : Activatable.t -> string option
|
||||
|
||||
val apValue : Activatable.t -> Activatable_Shared.ApValue.t option
|
||||
|
||||
val apValue' : Activatable.t -> int OneOrMany.t option
|
||||
|
||||
val max : Activatable.t -> int option
|
||||
@@ -0,0 +1,60 @@
|
||||
type t = {
|
||||
naming: Activatable_Active_Name.combinedName,
|
||||
active: Activatable_Convert.singleWithId,
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
validation: Activatable_Active_Validation.t,
|
||||
staticEntry: Activatable.t,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
};
|
||||
|
||||
let getActive =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
~addLevelToName,
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
) => {
|
||||
let naming =
|
||||
Activatable_Active_Name.getName(
|
||||
~addLevelToName,
|
||||
staticData,
|
||||
staticEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
let validation =
|
||||
Activatable_Active_Validation.isRemovalOrModificationValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
let {Activatable_Active_AdventurePointValue.apValue, isAutomatic} =
|
||||
Activatable_Active_AdventurePointValue.getApValueDifferenceOnChange(
|
||||
~isEntryToAdd,
|
||||
~automaticAdvantages=cache.automaticAdvantages,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
);
|
||||
|
||||
{
|
||||
naming,
|
||||
active: singleEntry,
|
||||
apValue,
|
||||
isAutomatic,
|
||||
validation,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
type t = {
|
||||
naming: Activatable_Active_Name.combinedName,
|
||||
active: Activatable_Convert.singleWithId,
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
validation: Activatable_Active_Validation.t,
|
||||
staticEntry: Activatable.t,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getActive cache staticData hero staticEntry maybeHeroEntry` adds display
|
||||
* options to an active entry, such as calculated AP, combined/generated name or
|
||||
* if it's valid to remove it.
|
||||
*/
|
||||
let getActive:
|
||||
(
|
||||
~isEntryToAdd: bool,
|
||||
~addLevelToName: bool,
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
Activatable_Convert.singleWithId
|
||||
) =>
|
||||
t;
|
||||
@@ -0,0 +1,590 @@
|
||||
module L = Ley_List;
|
||||
|
||||
open Ley_Option.Infix;
|
||||
open Ley_Function;
|
||||
open Activatable_Convert;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
type combinedApValue = {
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
let ensureFlat =
|
||||
fun
|
||||
| Advantage.Static.Flat(x) => Some(x)
|
||||
| PerLevel(_) => None;
|
||||
|
||||
let ensurePerLevel =
|
||||
fun
|
||||
| Advantage.Static.Flat(_) => None
|
||||
| PerLevel(x) => Some(x);
|
||||
|
||||
let getDefaultApValue = (staticEntry, singleHeroEntry) => {
|
||||
open Ley_List;
|
||||
|
||||
let sid1 = singleHeroEntry |> getOption1;
|
||||
let level = Ley_Option.fromOption(1, singleHeroEntry.level);
|
||||
let apValue =
|
||||
staticEntry
|
||||
|> Activatable_Accessors.apValue
|
||||
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
|
||||
|
||||
let optionApValue =
|
||||
sid1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOptionCost(staticEntry);
|
||||
|
||||
switch (optionApValue) {
|
||||
| Some(x) => Some(x)
|
||||
| None =>
|
||||
switch (apValue) {
|
||||
| Flat(x) => Some(x * level)
|
||||
| PerLevel(xs) =>
|
||||
switch (staticEntry) {
|
||||
| Advantage(_)
|
||||
| Disadvantage(_) => Ley_List.Safe.atMay(xs, level - 1)
|
||||
| SpecialAbility(_) =>
|
||||
xs |> take(Ley_Int.max(1, level)) |> Ley_List.sum |> (x => Some(x))
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let getPrinciplesObligationsMaxLevels = ({active, _}: Activatable_Dynamic.t) =>
|
||||
active
|
||||
|> Ley_List.foldr(
|
||||
(active: Activatable_Dynamic.single, (prevMax, prevSndMax)) =>
|
||||
switch (active.level, active.customCost) {
|
||||
// Only get the maximum from the current and the previous level, if
|
||||
// the current has no custom cost
|
||||
| (Some(activeLevel), None) =>
|
||||
if (activeLevel > prevMax) {
|
||||
(activeLevel, prevMax);
|
||||
} else {
|
||||
(prevMax, prevSndMax);
|
||||
}
|
||||
// Otherwise always return the previous max
|
||||
| _ => (prevMax, prevSndMax)
|
||||
},
|
||||
(0, 0),
|
||||
);
|
||||
|
||||
module IcAsIndex = {
|
||||
// Helper function to lookup entry, it's IC, converting it to an index and get
|
||||
// the correct value from the list of AP values
|
||||
let%private getApValueByIcAsIndexAux = (mp, getIc, id, apValues) =>
|
||||
Ley_IntMap.lookup(id, mp)
|
||||
>>= (
|
||||
static =>
|
||||
static |> getIc |> IC.icToIndex |> Ley_List.Safe.atMay(apValues)
|
||||
);
|
||||
|
||||
// Shortcut function for skills
|
||||
let%private getApValueFromSkillsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.skills, skill => skill.ic);
|
||||
|
||||
// Shortcut function for combat techniques
|
||||
let%private getApValueFromCombatTechniquesMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.combatTechniques, skill => skill.ic);
|
||||
|
||||
// Shortcut function for spells
|
||||
let%private getApValueFromSpellsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.spells, skill => skill.ic);
|
||||
|
||||
// Shortcut function for liturgical chants
|
||||
let%private getApValueFromLiturgicalChantsMapByIc = (staticData: Static.t) =>
|
||||
getApValueByIcAsIndexAux(staticData.liturgicalChants, skill => skill.ic);
|
||||
|
||||
/**
|
||||
* `getApValueByIcAsIndex staticData apValue sid` takes the static data, the
|
||||
* AP value of the current entry and it's first option. It uses the IC of the
|
||||
* entry defined by the option as an index to retrieve the AP value from the
|
||||
* list of AP values defined for the entry. If the selected entry is not
|
||||
* present or if the entry does not define a list of AP values, `None` is
|
||||
* returned.
|
||||
*/
|
||||
let getApValueByIcAsIndex =
|
||||
(
|
||||
staticData: Static.t,
|
||||
apValue: Advantage.Static.apValue,
|
||||
sid: Id.Activatable.Option.t,
|
||||
) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (sid, apValue) {
|
||||
| (Preset(Skill(id)), PerLevel(apValues)) =>
|
||||
getApValueFromSkillsMapByIc(staticData, id, apValues)
|
||||
| (Preset(CombatTechnique(id)), PerLevel(apValues)) =>
|
||||
getApValueFromCombatTechniquesMapByIc(staticData, id, apValues)
|
||||
| (Preset(Spell(id)), PerLevel(apValues)) =>
|
||||
getApValueFromSpellsMapByIc(staticData, id, apValues)
|
||||
| (Preset(LiturgicalChant(id)), PerLevel(apValues)) =>
|
||||
getApValueFromLiturgicalChantsMapByIc(staticData, id, apValues)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value(s) how the spent AP value would change after removing the
|
||||
* respective entry.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
let getApValueDifferenceOnChangeByEntry =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleHeroEntry,
|
||||
) => {
|
||||
open Ley_List;
|
||||
|
||||
let sid1 = singleHeroEntry |> getOption1;
|
||||
let level = singleHeroEntry.level;
|
||||
let apValue =
|
||||
staticEntry
|
||||
|> Activatable_Accessors.apValue
|
||||
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
|
||||
| Advantage(Aptitude)
|
||||
| Advantage(ExceptionalSkill)
|
||||
| Advantage(ExceptionalCombatTechnique)
|
||||
| Advantage(WeaponAptitude)
|
||||
| Disadvantage(Incompetent)
|
||||
| SpecialAbility(AdaptionZauber)
|
||||
| SpecialAbility(FavoriteSpellwork)
|
||||
| SpecialAbility(Lieblingsliturgie)
|
||||
| SpecialAbility(Forschungsgebiet)
|
||||
| SpecialAbility(Expertenwissen)
|
||||
| SpecialAbility(Wissensdurst)
|
||||
| SpecialAbility(WegDerGelehrten)
|
||||
| SpecialAbility(WegDerKuenstlerin)
|
||||
| SpecialAbility(Fachwissen) =>
|
||||
singleHeroEntry
|
||||
|> getOption1
|
||||
>>= IcAsIndex.getApValueByIcAsIndex(staticData, apValue)
|
||||
| Disadvantage(PersonalityFlaw) =>
|
||||
switch (sid1) {
|
||||
| Some(Preset(Generic(selected_option))) =>
|
||||
let matchOption = (target_option, current) =>
|
||||
switch ((current: option(Id.Activatable.Option.t))) {
|
||||
| Some(Preset(Generic(x))) => x === target_option
|
||||
| _ => false
|
||||
};
|
||||
|
||||
let isPersonalityFlawNotPaid = (target_option, paid_entries_max) =>
|
||||
target_option === selected_option
|
||||
&& Ley_List.countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
|> matchOption(target_option)
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? paid_entries_max - 1 : paid_entries_max);
|
||||
|
||||
// 7 = "Prejudice" => more than one entry possible
|
||||
// more than one entry of Prejudice does not contribute to AP spent
|
||||
//
|
||||
// 8 = "Unworldly" => more than one entry possible
|
||||
// more than two entries of Unworldly do not contribute to AP spent
|
||||
//
|
||||
// In both cases, removing the entry would not change AP so it has to
|
||||
// return 0.
|
||||
if (isPersonalityFlawNotPaid(7, 1) || isPersonalityFlawNotPaid(8, 2)) {
|
||||
Some(0);
|
||||
} else {
|
||||
getSelectOptionCost(staticEntry, Generic(selected_option));
|
||||
};
|
||||
| _ => None
|
||||
}
|
||||
| Disadvantage(Principles)
|
||||
| Disadvantage(Obligations) =>
|
||||
level
|
||||
>>= (
|
||||
level => {
|
||||
// This is the highest and the second-highest level of this entry at the
|
||||
// moment.
|
||||
let (maxLevel, sndMaxLevel) =
|
||||
getPrinciplesObligationsMaxLevels(heroEntry);
|
||||
|
||||
// If there is more than one entry on the same level if this entry is
|
||||
// active, it won't affect AP spent at all. Thus, if the entry is to
|
||||
// be added, there must be at least one (> 0) entry for this rule to
|
||||
// take effect.
|
||||
//
|
||||
// If the entry is not the one with the highest level, adding or
|
||||
// removing it won't affect AP spent at all
|
||||
if (maxLevel > level
|
||||
|| countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.elem(level, e.level),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? 0 : 1)) {
|
||||
None;
|
||||
} else {
|
||||
// Otherwise, the level difference results in the cost.
|
||||
apValue |> ensureFlat <&> ( * )(level - sndMaxLevel);
|
||||
};
|
||||
}
|
||||
)
|
||||
| Disadvantage(BadHabit) =>
|
||||
apValue
|
||||
|> ensureFlat
|
||||
|> Ley_Option.find(_ =>
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
> (isEntryToAdd ? 2 : 3)
|
||||
)
|
||||
| SpecialAbility(SkillSpecialization) =>
|
||||
sid1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (
|
||||
skill =>
|
||||
// Multiply number of final occurences of the
|
||||
// same skill...
|
||||
(
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
|> Ley_Option.elem(
|
||||
Id.Activatable.Option.Preset(Skill(skill.id)),
|
||||
)
|
||||
// Entries with custom cost are ignored for the rule
|
||||
&& Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
)
|
||||
+ (isEntryToAdd ? 1 : 0)
|
||||
)
|
||||
// ...with the skill's IC
|
||||
* IC.getApForActivatation(skill.ic)
|
||||
)
|
||||
| SpecialAbility(Language) =>
|
||||
level
|
||||
>>= (
|
||||
fun
|
||||
// Native Tongue (level 4) does not cost anything
|
||||
| 4 => Some(0)
|
||||
| level => apValue |> ensureFlat <&> ( * )(level)
|
||||
)
|
||||
| SpecialAbility(PropertyKnowledge)
|
||||
| SpecialAbility(AspectKnowledge) =>
|
||||
apValue
|
||||
|> ensurePerLevel
|
||||
>>= (
|
||||
apPerLevel => {
|
||||
// Ignore custom cost activations in terms of calculated cost
|
||||
let amountActive =
|
||||
countBy(
|
||||
(e: Activatable_Dynamic.single) =>
|
||||
Ley_Option.isNone(e.customCost),
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
let index = amountActive + (isEntryToAdd ? 0 : (-1));
|
||||
|
||||
Ley_List.Safe.atMay(apPerLevel, index);
|
||||
}
|
||||
)
|
||||
| SpecialAbility(TraditionWitches) =>
|
||||
// There are two disadvantages that, when active, decrease the cost of
|
||||
// this tradition by 10 AP each
|
||||
let decreaseCost = (id, cost) =>
|
||||
hero.disadvantages
|
||||
|> Ley_IntMap.lookup(id)
|
||||
|> Activatable_Accessors.isActiveM
|
||||
? cost - 10 : cost;
|
||||
|
||||
apValue
|
||||
|> ensureFlat
|
||||
<&> (
|
||||
flatAp =>
|
||||
flatAp
|
||||
|> decreaseCost(Id.Disadvantage.toInt(NoFlyingBalm))
|
||||
|> decreaseCost(Id.Disadvantage.toInt(NoFamiliar))
|
||||
);
|
||||
| SpecialAbility(Recherchegespuer) =>
|
||||
// The AP cost for this SA consist of two parts: AP based on the IC of
|
||||
// the main subject (from "SA_531"/Wissensdurst) in addition to AP based
|
||||
// on the IC of the side subject selected in this SA.
|
||||
|
||||
hero.specialAbilities
|
||||
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Wissensdurst))
|
||||
>>= (
|
||||
wissensdurst =>
|
||||
apValue
|
||||
|> ensurePerLevel
|
||||
>>= (
|
||||
apPerLevel => {
|
||||
let getCostFromHeroEntry = entry =>
|
||||
entry
|
||||
|> getOption1
|
||||
>>= IcAsIndex.getApValueByIcAsIndex(
|
||||
staticData,
|
||||
PerLevel(apPerLevel),
|
||||
);
|
||||
|
||||
Ley_Option.liftM2(
|
||||
(+),
|
||||
// Cost for side subject
|
||||
getCostFromHeroEntry(singleHeroEntry),
|
||||
// Cost for main subject from Wissensdurst
|
||||
wissensdurst.active
|
||||
|> Ley_Option.listToOption
|
||||
>>= (
|
||||
fst =>
|
||||
fst
|
||||
|> singleToSingleWithId(heroEntry, 0)
|
||||
|> getCostFromHeroEntry
|
||||
),
|
||||
);
|
||||
}
|
||||
)
|
||||
)
|
||||
| SpecialAbility(LanguageSpecializations) =>
|
||||
apValue
|
||||
|> ensureFlat
|
||||
>>= (
|
||||
flatAp =>
|
||||
sid1
|
||||
>>= getGenericId
|
||||
>>= (
|
||||
languageId =>
|
||||
hero.specialAbilities
|
||||
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Language))
|
||||
>>= (
|
||||
language =>
|
||||
language.active
|
||||
|> Ley_List.find((e: Activatable_Dynamic.single) =>
|
||||
e.options
|
||||
|> Ley_Option.listToOption
|
||||
>>= getGenericId
|
||||
|> Ley_Option.elem(languageId)
|
||||
)
|
||||
>>= (
|
||||
selectedLanguage =>
|
||||
selectedLanguage.level
|
||||
<&> (
|
||||
fun
|
||||
| 4 => 0
|
||||
| _ => flatAp
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
| SpecialAbility(Handwerkskunst)
|
||||
| SpecialAbility(KindDerNatur)
|
||||
| SpecialAbility(KoerperlichesGeschick)
|
||||
| SpecialAbility(SozialeKompetenz)
|
||||
| SpecialAbility(Universalgenie) =>
|
||||
singleHeroEntry.options
|
||||
|> Ley_List.take(3)
|
||||
|> Ley_Option.mapOption(
|
||||
IcAsIndex.getApValueByIcAsIndex(staticData, apValue),
|
||||
)
|
||||
|> Ley_List.sum
|
||||
|> Ley_Option.ensure((<)(0))
|
||||
| _ => getDefaultApValue(staticEntry, singleHeroEntry)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the AP you get when addiing or removing the entry.
|
||||
*
|
||||
* @param isEntryToAdd If `entry` has not been added to the list of active
|
||||
* entries yet, this must be `true`, otherwise `false`.
|
||||
*/
|
||||
let getApValueDifferenceOnChange =
|
||||
(
|
||||
~isEntryToAdd,
|
||||
~automaticAdvantages,
|
||||
staticData,
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleHeroEntry,
|
||||
) => {
|
||||
let isAutomatic = Ley_List.elem(singleHeroEntry.id, automaticAdvantages);
|
||||
|
||||
let modifyAbs =
|
||||
switch (staticEntry) {
|
||||
| Activatable.Disadvantage(_) => Ley_Int.negate
|
||||
| Advantage(_)
|
||||
| SpecialAbility(_) => id
|
||||
};
|
||||
|
||||
switch (singleHeroEntry.customCost) {
|
||||
| Some(customCost) => {apValue: modifyAbs(customCost), isAutomatic}
|
||||
| None =>
|
||||
getApValueDifferenceOnChangeByEntry(
|
||||
~isEntryToAdd,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleHeroEntry,
|
||||
)
|
||||
|> Ley_Option.fromOption(0)
|
||||
|> modifyAbs
|
||||
|> (apValue => {apValue, isAutomatic})
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the difference to the value from `getApValueDifferenceOnChange` to be
|
||||
* able to calculate the correct AP spent.
|
||||
*
|
||||
* A positive return value means that the entry uses *more* AP than what
|
||||
* `getApValueDifferenceOnChange` returned for all activations, so that both
|
||||
* return values can be added to form the final AP spent.
|
||||
*
|
||||
* Note, that disadvantages still return positive values if they actually cost
|
||||
* AP.
|
||||
*/
|
||||
let getApSpentDifference = (staticEntry, heroEntry: Activatable_Dynamic.t) => {
|
||||
let apValue = staticEntry |> Activatable_Accessors.apValue;
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
|
||||
| Disadvantage(Principles)
|
||||
| Disadvantage(Obligations) =>
|
||||
let (maxLevel, sndMaxLevel) =
|
||||
getPrinciplesObligationsMaxLevels(heroEntry);
|
||||
|
||||
let singlesAtMaxLevel =
|
||||
L.countBy(
|
||||
fun
|
||||
| ({level: Some(level), _}: Activatable_Dynamic.single) =>
|
||||
level === maxLevel
|
||||
| _ => false,
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
let baseApValue =
|
||||
switch (apValue) {
|
||||
| Some(Flat(value)) => value
|
||||
| Some(PerLevel(_))
|
||||
| None => 0
|
||||
};
|
||||
|
||||
singlesAtMaxLevel > 1
|
||||
// In this case, all max level entries would be 0, so this needs to be
|
||||
// the full value for the max level
|
||||
? baseApValue * maxLevel
|
||||
// otherwise, the max level entry costs the difference between max and
|
||||
// second max, so we need to fill to actual value so that
|
||||
// currentDifference + baseApValue * sndMaxLevel = total AP value
|
||||
: baseApValue * sndMaxLevel;
|
||||
| Disadvantage(PersonalityFlaw) =>
|
||||
let counterBySelectOption =
|
||||
heroEntry.active
|
||||
|> SelectOption.Map.countByM(
|
||||
fun
|
||||
| (
|
||||
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
|
||||
) =>
|
||||
Some(id)
|
||||
| _ => None,
|
||||
);
|
||||
|
||||
let getDiffBySelectOption = (selectOptionId, paidEntries, counter) => {
|
||||
counter
|
||||
|> SelectOption.Map.lookup(Generic(selectOptionId))
|
||||
|> Ley_Option.fromOption(0) > paidEntries
|
||||
? Activatable_SelectOptions.getSelectOptionCost(
|
||||
staticEntry,
|
||||
Generic(selectOptionId),
|
||||
)
|
||||
|> Ley_Option.option(0, apValue =>
|
||||
apValue * paidEntries |> Ley_Int.negate
|
||||
)
|
||||
: 0;
|
||||
};
|
||||
|
||||
getDiffBySelectOption(7, 1, counterBySelectOption)
|
||||
+ getDiffBySelectOption(8, 2, counterBySelectOption);
|
||||
| Disadvantage(BadHabit) =>
|
||||
heroEntry.active
|
||||
// Ignore entries with custom cost
|
||||
|> Ley_List.countBy(
|
||||
fun
|
||||
| ({customCost: None, _}: Activatable_Dynamic.single) => true
|
||||
| _ => false,
|
||||
)
|
||||
> 3
|
||||
// if more than three entries are active, a 0 is displayed, so we need to
|
||||
// keep the AP value for three entries.
|
||||
? switch (apValue) {
|
||||
| Some(Flat(flatApValue)) => flatApValue * (-3)
|
||||
| _ => 0
|
||||
}
|
||||
: 0
|
||||
| SpecialAbility(SkillSpecialization) =>
|
||||
let counterBySelectOption =
|
||||
heroEntry.active
|
||||
|> SelectOption.Map.countByM(
|
||||
fun
|
||||
| (
|
||||
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
|
||||
) =>
|
||||
Some(id)
|
||||
| _ => None,
|
||||
);
|
||||
|
||||
counterBySelectOption
|
||||
|> SelectOption.Map.foldrWithKey(
|
||||
(id, count, accDiff) => {
|
||||
switch (
|
||||
Activatable_SelectOptions.getSelectOption(staticEntry, id)
|
||||
) {
|
||||
| Some({staticEntry: Some(Skill(skill)), _}) =>
|
||||
// The sum of displayed AP values without taking IC into account.
|
||||
let currentIcFactorForSkill = count * count;
|
||||
// The sum of actual AP values without taking IC into account.
|
||||
let actualIcFactorForSkill = Math.gsum(1, count);
|
||||
|
||||
// Apply IC multiplier to get final AP value
|
||||
IC.getApForActivatation(skill.ic)
|
||||
* (actualIcFactorForSkill - currentIcFactorForSkill)
|
||||
+ accDiff;
|
||||
| _ => accDiff
|
||||
}
|
||||
},
|
||||
0,
|
||||
);
|
||||
| SpecialAbility(PropertyKnowledge)
|
||||
| SpecialAbility(AspectKnowledge) =>
|
||||
let entryCount = heroEntry.active |> Ley_List.length;
|
||||
|
||||
switch (apValue) {
|
||||
| Some(PerLevel(apValues)) =>
|
||||
let currentSum =
|
||||
Ley_List.Safe.atMay(apValues, entryCount)
|
||||
|> Ley_Option.option(0, ( * )(entryCount));
|
||||
let actualSum = apValues |> Ley_List.take(entryCount) |> Ley_List.sum;
|
||||
|
||||
actualSum - currentSum;
|
||||
| _ => 0
|
||||
};
|
||||
| _ => 0
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Functions for working on adventure point values for Activatable entries.
|
||||
*
|
||||
* This module provides types and functions for calculating and keeping
|
||||
* adventure point value calculations in genereal and specific to certain
|
||||
* entries.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* The generated AP value for an Activatable. Contains the generated AP value
|
||||
* as well as if the entry is automatically added by the race, in which case
|
||||
* no additional AP from the hero are needed.
|
||||
*/
|
||||
type combinedApValue = {
|
||||
apValue: int,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getApValue isEntryToAdd automaticAdvantages staticData hero
|
||||
* staticActivatable heroActivatable singleEntry` returns the AP you get when
|
||||
* removing the passed `singleEntry`. It also returns if the entry has been
|
||||
* automatically granted by the race.
|
||||
*
|
||||
* `isEntryToAdd` has to be `true` if `singleEntry` has not been added
|
||||
* to the list of active entries yet, otherwise `false`. `automaticAdvantages`
|
||||
* is the list of automatic advantage IDs.
|
||||
*/
|
||||
let getApValueDifferenceOnChange:
|
||||
(
|
||||
~isEntryToAdd: bool,
|
||||
~automaticAdvantages: list(int),
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
singleWithId
|
||||
) =>
|
||||
combinedApValue;
|
||||
@@ -0,0 +1,507 @@
|
||||
open Ley_Option;
|
||||
open Ley_Option.Infix;
|
||||
open Ley_Function;
|
||||
open Activatable_Convert;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
let getDefaultNameAddition = (staticEntry, heroEntry) => {
|
||||
let input = Activatable_Accessors.input(staticEntry);
|
||||
let selectOptions = Activatable_Accessors.selectOptions(staticEntry);
|
||||
|
||||
let sid = heroEntry |> getOption1;
|
||||
let sid2 = heroEntry |> getOption2;
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (input, sid, sid2) {
|
||||
// Text input
|
||||
| (Some(_), Some(CustomInput(str)), None) => Some(str)
|
||||
// Select option and text input
|
||||
| (Some(_), Some(Preset(id)), Some(CustomInput(str)))
|
||||
when SelectOption.Map.size(selectOptions) > 0 =>
|
||||
Some(
|
||||
(id |> getSelectOptionName(staticEntry) |> fromOption(""))
|
||||
++ ": "
|
||||
++ str,
|
||||
)
|
||||
// Plain select option
|
||||
| (None, Some(Preset(id)), None) => getSelectOptionName(staticEntry, id)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* A lot of entries have customization options: Text input, select option or
|
||||
* both. This function creates a string that can be appended to the `name`
|
||||
* property of the respective record to create the full active name.
|
||||
*/
|
||||
let getEntrySpecificNameAddition =
|
||||
(staticData: Static.t, staticEntry, heroEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(entry.id)) {
|
||||
| Aptitude
|
||||
| ExceptionalSkill =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(Skill(id)) =>
|
||||
lookupMap(id, staticData.skills, x => x.name)
|
||||
| Preset(Spell(id)) =>
|
||||
lookupMap(id, staticData.spells, x => x.name)
|
||||
| Preset(LiturgicalChant(id)) =>
|
||||
lookupMap(id, staticData.liturgicalChants, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| ExceptionalCombatTechnique
|
||||
| WeaponAptitude =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(CombatTechnique(id)) =>
|
||||
lookupMap(id, staticData.combatTechniques, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| HatredFor =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
|> liftM2(
|
||||
(type_, frequency: SelectOption.t) =>
|
||||
type_ ++ " (" ++ frequency.name ++ ")",
|
||||
getOption2(heroEntry) >>= getCustomInput,
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
| Disadvantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(entry.id)) {
|
||||
| Incompetent =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (x => x.name)
|
||||
| PersonalityFlaw =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
<&> (
|
||||
option1 =>
|
||||
(
|
||||
switch (option1.id) {
|
||||
// Get the input if Prejudice or Unworldly is selected
|
||||
| Generic(7 | 8) => heroEntry |> getOption2 >>= getCustomInput
|
||||
// Otherwise ignore any additional options
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|> option(option1.name, specialInput =>
|
||||
option1.name ++ ": " ++ specialInput
|
||||
)
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
| SpecialAbility(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(entry.id)) {
|
||||
| AdaptionZauber
|
||||
| FavoriteSpellwork =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(Spell(id)) =>
|
||||
lookupMap(id, staticData.spells, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| TraditionSavant
|
||||
| Forschungsgebiet
|
||||
| Expertenwissen
|
||||
| Wissensdurst
|
||||
| Recherchegespuer =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
<&> (x => x.name)
|
||||
| Lieblingsliturgie =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= (
|
||||
sid =>
|
||||
switch (sid) {
|
||||
| Preset(LiturgicalChant(id)) =>
|
||||
lookupMap(id, staticData.liturgicalChants, x => x.name)
|
||||
| Preset(_)
|
||||
| CustomInput(_) => None
|
||||
}
|
||||
)
|
||||
| SkillSpecialization =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
>>= (
|
||||
skill =>
|
||||
heroEntry
|
||||
|> getOption2
|
||||
>>= (
|
||||
option2 =>
|
||||
(
|
||||
switch (option2) {
|
||||
// If input string use input
|
||||
| CustomInput(x) => Some(x)
|
||||
// Otherwise lookup application name
|
||||
| Preset(Generic(id)) =>
|
||||
skill.applications
|
||||
|> Ley_IntMap.find((a: Skill.Static.Application.t) =>
|
||||
a.id === id
|
||||
)
|
||||
<&> (a => a.name)
|
||||
| Preset(_) => None
|
||||
}
|
||||
)
|
||||
// Merge skill name and application name
|
||||
<&> (appName => skill.name ++ ": " ++ appName)
|
||||
)
|
||||
)
|
||||
| Exorzist =>
|
||||
switch (heroEntry.level) {
|
||||
| Some(1) =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOptionName(staticEntry)
|
||||
| _ => None
|
||||
}
|
||||
| SpellEnhancement as entryId
|
||||
| ChantEnhancement as entryId =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
>>= getSelectOption(staticEntry)
|
||||
>>= (
|
||||
enhancement =>
|
||||
enhancement.enhancementTarget
|
||||
>>= (
|
||||
id =>
|
||||
(
|
||||
switch (entryId) {
|
||||
| SpellEnhancement =>
|
||||
Ley_IntMap.lookup(id, staticData.spells) <&> (x => x.name)
|
||||
| _ =>
|
||||
Ley_IntMap.lookup(id, staticData.liturgicalChants)
|
||||
<&> (x => x.name)
|
||||
}
|
||||
)
|
||||
<&> (targetName => targetName ++ ": " ++ enhancement.name)
|
||||
)
|
||||
)
|
||||
| TraditionArcaneBard =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getGenericId
|
||||
>>= flip(Ley_IntMap.lookup, staticData.arcaneBardTraditions)
|
||||
<&> (trad => trad.name)
|
||||
| TraditionArcaneDancer =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getGenericId
|
||||
>>= flip(Ley_IntMap.lookup, staticData.arcaneDancerTraditions)
|
||||
<&> (trad => trad.name)
|
||||
| LanguageSpecializations =>
|
||||
liftM2(
|
||||
getSelectOption,
|
||||
Ley_IntMap.lookup(
|
||||
Id.SpecialAbility.toInt(Language),
|
||||
staticData.specialAbilities,
|
||||
)
|
||||
<&> (specialAbility => Activatable.SpecialAbility(specialAbility)),
|
||||
getOption1(heroEntry)
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId,
|
||||
)
|
||||
|> join
|
||||
>>= (
|
||||
language =>
|
||||
heroEntry
|
||||
|> getOption2
|
||||
>>= (
|
||||
option2 =>
|
||||
(
|
||||
switch (option2) {
|
||||
| CustomInput(str) => Some(str)
|
||||
| Preset(Generic(specializationId)) =>
|
||||
language.specializations
|
||||
>>= (
|
||||
specializations =>
|
||||
Ley_List.Safe.atMay(
|
||||
specializations,
|
||||
specializationId - 1,
|
||||
)
|
||||
)
|
||||
| Preset(_) => None
|
||||
}
|
||||
)
|
||||
<&> (specialization => language.name ++ ": " ++ specialization)
|
||||
)
|
||||
)
|
||||
| Fachwissen =>
|
||||
heroEntry
|
||||
|> getOption1
|
||||
>>= getSkillFromOption(staticData)
|
||||
>>= (
|
||||
skill => {
|
||||
let applications =
|
||||
skill.applications
|
||||
|> Ley_IntMap.filter((app: Skill.Static.Application.t) =>
|
||||
app.prerequisite |> isNone
|
||||
);
|
||||
|
||||
[heroEntry |> getOption2, heroEntry |> getOption3]
|
||||
|> mapOption(option =>
|
||||
option
|
||||
>>= getGenericId
|
||||
>>= (
|
||||
opt =>
|
||||
applications
|
||||
|> Ley_IntMap.find((app: Skill.Static.Application.t) =>
|
||||
app.id === opt
|
||||
)
|
||||
<&> (app => app.name)
|
||||
)
|
||||
)
|
||||
|> ensure(apps => apps |> Ley_List.length |> (===)(2))
|
||||
<&> (
|
||||
apps =>
|
||||
apps
|
||||
|> AdvancedFiltering.sortStrings(staticData)
|
||||
|> Intl.ListFormat.format(Conjunction, staticData)
|
||||
|> (appsStr => skill.name ++ ": " ++ appsStr)
|
||||
);
|
||||
}
|
||||
)
|
||||
| _ => getDefaultNameAddition(staticEntry, heroEntry)
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getDisAdvLevelStr = RomanInt.intToRoman;
|
||||
|
||||
let getSpecialAbilityLevelStr = level =>
|
||||
(level > 1 ? "I" ++ Chars.nobr ++ "–" ++ Chars.nobr : "")
|
||||
++ getDisAdvLevelStr(level);
|
||||
|
||||
/**
|
||||
* Gets the level string that hast to be appended (with a non-breaking space!)
|
||||
* to the name. This string is aware of differences between dis/advantages and
|
||||
* special abilties as well as it handles the Native Tongue level for
|
||||
* languages.
|
||||
*/
|
||||
let getLevelName = (staticData: Static.t, staticEntry, singleHeroEntry) =>
|
||||
switch (staticEntry, singleHeroEntry.level) {
|
||||
| (Activatable.Advantage(_), Some(level))
|
||||
| (Disadvantage(_), Some(level)) => Some(getDisAdvLevelStr(level))
|
||||
| (SpecialAbility(staticEntry), Some(level)) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(staticEntry.id)) {
|
||||
// Language level 4 = Native Tongue and thus needs a special name
|
||||
| Language when level === 4 =>
|
||||
Some(staticData.messages.specialabilities_nativetonguelevel)
|
||||
| _ => Some(getSpecialAbilityLevelStr(level))
|
||||
}
|
||||
)
|
||||
| (Advantage(_) | Disadvantage(_) | SpecialAbility(_), None) => None
|
||||
};
|
||||
|
||||
/**
|
||||
* Some entries cannot use the default `name` property from wiki entries. The
|
||||
* value returned by may not use the default `name` property. For all entries
|
||||
* that do not need to handle a specific display format, the default `name`
|
||||
* property is used.
|
||||
*/
|
||||
let getEntrySpecificNameReplacements =
|
||||
(~addLevelToName, staticEntry, nameAddition, levelName) => {
|
||||
let name = Activatable_Accessors.name(staticEntry);
|
||||
|
||||
let flatLevelName =
|
||||
addLevelToName ? option("", (++)(Chars.nbsp), levelName) : "";
|
||||
|
||||
let mapNameAddition = f => option(name, f, nameAddition);
|
||||
|
||||
let mapDefaultWithParens = () =>
|
||||
mapNameAddition(add => name ++ " (" ++ add ++ ")");
|
||||
|
||||
let mapDefaultWithoutParens = () =>
|
||||
mapNameAddition(add => name ++ " " ++ add);
|
||||
|
||||
let addSndInParens = snd =>
|
||||
Ley_List.Extra.replaceStr(")", ": " ++ snd ++ ")");
|
||||
|
||||
switch (staticEntry) {
|
||||
| Advantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(entry.id)) {
|
||||
| ImmunityToPoison
|
||||
| ImmunityToDisease
|
||||
| HatredFor => mapDefaultWithoutParens()
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
| Disadvantage(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(entry.id)) {
|
||||
| AfraidOf => mapDefaultWithoutParens() ++ flatLevelName
|
||||
| Principles
|
||||
| Obligations =>
|
||||
option(
|
||||
name ++ flatLevelName,
|
||||
nameAddition => name ++ flatLevelName ++ " (" ++ nameAddition ++ ")",
|
||||
nameAddition,
|
||||
)
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
| SpecialAbility(entry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(entry.id)) {
|
||||
| GebieterDesAspekts => mapDefaultWithoutParens()
|
||||
| TraditionArcaneBard
|
||||
| TraditionArcaneDancer
|
||||
| TraditionSavant => mapNameAddition(flip(addSndInParens, name))
|
||||
| _ => mapDefaultWithParens() ++ flatLevelName
|
||||
}
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
type combinedName = {
|
||||
name: string,
|
||||
baseName: string,
|
||||
addName: option(string),
|
||||
levelName: option(string),
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns name, splitted and combined, of advantage/disadvantage/special
|
||||
* ability as a Option (in case the wiki entry does not exist).
|
||||
*/
|
||||
let getName = (~addLevelToName, staticData, staticEntry, heroEntry) => {
|
||||
let addName =
|
||||
getEntrySpecificNameAddition(staticData, staticEntry, heroEntry);
|
||||
|
||||
let levelName = getLevelName(staticData, staticEntry, heroEntry);
|
||||
|
||||
let fullName =
|
||||
getEntrySpecificNameReplacements(
|
||||
~addLevelToName,
|
||||
staticEntry,
|
||||
addName,
|
||||
levelName,
|
||||
);
|
||||
|
||||
{
|
||||
name: fullName,
|
||||
baseName: Activatable_Accessors.name(staticEntry),
|
||||
addName,
|
||||
levelName,
|
||||
};
|
||||
};
|
||||
/*
|
||||
/**
|
||||
* Returns the name of the given object. If the object is a string, it returns
|
||||
* the string.
|
||||
*/
|
||||
export const getFullName =
|
||||
(obj: string | ActiveActivatable): string => {
|
||||
if (typeof obj === "string") {
|
||||
return obj
|
||||
}
|
||||
|
||||
return obj.nameAndCost.naming.name
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts the full special ability name and returns only the text between
|
||||
* parentheses. If no parentheses were found, returns an empty string.
|
||||
*/
|
||||
export const getBracketedNameFromFullName =
|
||||
(full_name: string): string => {
|
||||
const result = /\((?<subname>.+)\)/u .exec (full_name)
|
||||
|
||||
if (result === null || result .groups === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return result .groups .subname
|
||||
}
|
||||
|
||||
/**
|
||||
* `compressList :: L10n -> [ActiveActivatable] -> String`
|
||||
*
|
||||
* Takes a list of active Activatables and merges them together. Used to display
|
||||
* lists of Activatables on character sheet.
|
||||
*/
|
||||
export const compressList =
|
||||
(staticData: StaticDataRecord) =>
|
||||
(xs: List<Record<ActiveActivatable>>): string => {
|
||||
const grouped_xs =
|
||||
elems (groupByKey<Record<ActiveActivatable>, string> (AAA_.id) (xs))
|
||||
|
||||
return pipe (
|
||||
map (
|
||||
ifElse<List<Record<ActiveActivatable>>>
|
||||
(xs_group => flength (xs_group) === 1)
|
||||
(pipe (listToOption, Option ("") (AAA_.name)))
|
||||
(xs_group => pipe (
|
||||
map ((x: Record<ActiveActivatable>) => {
|
||||
const levelPart =
|
||||
pipe (
|
||||
AAA_.level,
|
||||
fmap (pipe (toRoman, appendStr (" "))),
|
||||
fromOption ("")
|
||||
)
|
||||
(x)
|
||||
|
||||
const selectOptionPart =
|
||||
fromOption ("") (AAA_.addName (x))
|
||||
|
||||
return selectOptionPart + levelPart
|
||||
}),
|
||||
sortStrings (staticData),
|
||||
intercalate (", "),
|
||||
x => ` (${x})`,
|
||||
x => option ("")
|
||||
((r: Record<ActiveActivatable>) =>
|
||||
AAA_.baseName (r) + x)
|
||||
(listToOption (xs_group))
|
||||
)
|
||||
(xs_group))
|
||||
),
|
||||
sortStrings (staticData),
|
||||
intercalate (", ")
|
||||
)
|
||||
(grouped_xs)
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Generate full names for an active Activatanle entry.
|
||||
*
|
||||
* This module provides types and functions to generate a full name string for
|
||||
* an active Activatable entry.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* The generated name for an Activatable. Contains the generated name and it's
|
||||
* parts: The base entry name, the generated addition and the converted level
|
||||
* name.
|
||||
*/
|
||||
type combinedName = {
|
||||
name: string,
|
||||
baseName: string,
|
||||
addName: option(string),
|
||||
levelName: option(string),
|
||||
};
|
||||
|
||||
/**
|
||||
* `getName addLevelToName staticData staticActivatable singleEntry` returns
|
||||
* the name, splitted and combined, of the passed `singleEntry`.
|
||||
*/
|
||||
let getName:
|
||||
(~addLevelToName: bool, Static.t, Activatable.t, singleWithId) =>
|
||||
combinedName;
|
||||
@@ -0,0 +1,449 @@
|
||||
module I = Ley_Int;
|
||||
module IM = Ley_IntMap;
|
||||
module IS = Ley_IntSet;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
type t = {
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
disabled: bool,
|
||||
};
|
||||
|
||||
// const hasRequiredMinimumLevel =
|
||||
// (min_level: Maybe<number>) => (max_level: Maybe<number>): boolean =>
|
||||
// isJust (max_level) && isJust (min_level)
|
||||
|
||||
let getActivesById = (mp, id) =>
|
||||
IM.lookup(id, mp)
|
||||
|> O.option([], ({active, _}: Activatable_Dynamic.t) => active);
|
||||
|
||||
let isNotRequiredByDependencies =
|
||||
(
|
||||
hero: Hero.t,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleEntry: Activatable_Convert.singleWithId,
|
||||
) =>
|
||||
heroEntry.dependencies
|
||||
// Filter out every dependency that can be matched by another entry
|
||||
|> Dependencies.Flatten.flattenActivatableDependencies(
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => getActivesById(hero.advantages)
|
||||
| Disadvantage(_) => getActivesById(hero.disadvantages)
|
||||
| SpecialAbility(_) => getActivesById(hero.specialAbilities)
|
||||
},
|
||||
singleEntry.id,
|
||||
)
|
||||
|> L.all((dep: Activatable_Dynamic.dependency) =>
|
||||
!
|
||||
Dependencies.Activatable.isDependencyMatched(
|
||||
dep,
|
||||
Activatable_Convert.singleWithIdToSingle(singleEntry),
|
||||
)
|
||||
// If the entry matched the dependency, try if other activations of the
|
||||
// same entry may still satisfy the dependency, so that it can still be
|
||||
// removed
|
||||
|| L.Index.deleteAt(singleEntry.index, heroEntry.active)
|
||||
|> L.any(Dependencies.Activatable.isDependencyMatched(dep))
|
||||
);
|
||||
|
||||
let isEntrySpecificRemovalValid =
|
||||
(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry: Activatable_Dynamic.t,
|
||||
singleEntry: Activatable_Convert.singleWithId,
|
||||
) =>
|
||||
if (Tradition.Magical.isTraditionId(staticData, singleEntry.id)) {
|
||||
let activeTraditions = cache.magicalTraditions;
|
||||
|
||||
let hasMultipleTraditions = L.length(activeTraditions) > 1;
|
||||
|
||||
hasMultipleTraditions
|
||||
|| !ActivatableSkills.hasActiveSkillEntries(Spells, hero)
|
||||
&& IS.null(hero.cantrips);
|
||||
} else if (Tradition.Blessed.isTraditionId(staticData, singleEntry.id)) {
|
||||
!ActivatableSkills.hasActiveSkillEntries(LiturgicalChants, hero)
|
||||
&& IS.null(hero.blessings);
|
||||
} else {
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Advantage.(
|
||||
switch (fromInt(staticAdvantage.id)) {
|
||||
| ExceptionalSkill =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Skill(id)), ..._] =>
|
||||
// value of target skill
|
||||
let value =
|
||||
IM.lookup(id, hero.skills) |> Skill.Dynamic.getValueDef;
|
||||
|
||||
// amount of active Exceptional Skill advantages for the same skill
|
||||
let countSameSkill =
|
||||
L.countBy(
|
||||
(active: Activatable_Dynamic.single) =>
|
||||
switch (active.options) {
|
||||
| [Preset(Skill(otherId)), ..._] => otherId === id
|
||||
| _ => false
|
||||
},
|
||||
heroEntry.active,
|
||||
);
|
||||
|
||||
// if the maximum skill rating is reached removal needs to be
|
||||
// disabled
|
||||
value >= cache.startExperienceLevel.maxSkillRating + countSameSkill;
|
||||
| _ => true
|
||||
}
|
||||
| ExceptionalCombatTechnique =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(CombatTechnique(id)), ..._] =>
|
||||
// value of target combat technique
|
||||
let value =
|
||||
IM.lookup(id, hero.combatTechniques)
|
||||
|> CombatTechnique.Dynamic.getValueDef;
|
||||
|
||||
// if the maximum combat technique rating is reached removal needs
|
||||
// to be disabled
|
||||
value >= cache.startExperienceLevel.maxCombatTechniqueRating + 1;
|
||||
| _ => true
|
||||
}
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| Literacy =>
|
||||
switch (
|
||||
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
|
||||
cache.matchingLanguagesScripts.scriptsWithMatchingLanguages,
|
||||
singleEntry.options,
|
||||
) {
|
||||
// requiring entry must be active, the list of scripts must contain
|
||||
// only one element and that element has to match the current script
|
||||
// to prohibit removing the entry
|
||||
| (
|
||||
true,
|
||||
[onlyScriptWithLanguage],
|
||||
[Preset(Generic(scriptId)), ..._],
|
||||
) =>
|
||||
onlyScriptWithLanguage !== scriptId
|
||||
| _ => true
|
||||
}
|
||||
| Language =>
|
||||
switch (
|
||||
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
|
||||
cache.matchingLanguagesScripts.languagesWithMatchingScripts,
|
||||
singleEntry.options,
|
||||
) {
|
||||
// requiring entry must be active, the list of languages must contain
|
||||
// only one element and that element has to match the current language
|
||||
// to prohibit removing the entry
|
||||
| (
|
||||
true,
|
||||
[onlyLanguageWithScript],
|
||||
[Preset(Generic(languageId)), ..._],
|
||||
) =>
|
||||
onlyLanguageWithScript !== languageId
|
||||
| _ => true
|
||||
}
|
||||
| PropertyKnowledge =>
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Generic(propertyId)), ..._] =>
|
||||
hero.spells
|
||||
// If there is any spell with matching property above SR 14...
|
||||
|> IM.any((heroSpell: ActivatableSkill.Dynamic.t) =>
|
||||
ActivatableSkill.Dynamic.valueToInt(heroSpell.value) > 14
|
||||
&& IM.lookup(heroSpell.id, staticData.spells)
|
||||
|> O.option(true, ({Spell.Static.property, _}) =>
|
||||
property === propertyId
|
||||
)
|
||||
)
|
||||
// ...prohibit removal
|
||||
|> (!)
|
||||
| _ => true
|
||||
}
|
||||
| AspectKnowledge =>
|
||||
let activeAspects =
|
||||
Activatable_SelectOptions.mapActiveOptions1(
|
||||
fun
|
||||
| Preset(Generic(aspectId)) => Some(aspectId)
|
||||
| _ => None,
|
||||
heroEntry,
|
||||
)
|
||||
|> IS.fromList;
|
||||
|
||||
switch (singleEntry.options) {
|
||||
| [Preset(Generic(aspectId)), ..._] =>
|
||||
let otherAspects = IS.delete(aspectId, activeAspects);
|
||||
|
||||
hero.liturgicalChants
|
||||
// If there is any liturgical chant with matching aspect
|
||||
// above SR 14 and no other aspect knowledges for that entry...
|
||||
|> IM.any((heroLiturgicalChant: ActivatableSkill.Dynamic.t) =>
|
||||
ActivatableSkill.Dynamic.valueToInt(
|
||||
heroLiturgicalChant.value,
|
||||
)
|
||||
> 14
|
||||
&& IM.lookup(
|
||||
heroLiturgicalChant.id,
|
||||
staticData.liturgicalChants,
|
||||
)
|
||||
|> O.option(true, ({LiturgicalChant.Static.aspects, _}) =>
|
||||
IS.member(aspectId, aspects)
|
||||
&& IS.disjoint(otherAspects, aspects)
|
||||
)
|
||||
)
|
||||
// ...prohibit removal
|
||||
|> (!);
|
||||
| _ => true
|
||||
};
|
||||
| CombatStyleCombination =>
|
||||
let activeArmedStyles = cache.armedCombatStylesCount;
|
||||
let activeUnarmedStyles = cache.unarmedCombatStylesCount;
|
||||
|
||||
let totalActive = activeArmedStyles + activeUnarmedStyles;
|
||||
|
||||
// default is 1 per group (armed/unarmed), but with this SA 1 more in
|
||||
// one group: maximum of 3, but max 2 per group. If max is reached,
|
||||
// this SA cannot be removed
|
||||
totalActive < 3 && (activeArmedStyles < 2 || activeUnarmedStyles < 2);
|
||||
| MagicStyleCombination =>
|
||||
// default is 1, but with this SA its 2. If it's 2 this SA is
|
||||
// neccessary and cannot be removed
|
||||
cache.magicalStylesCount < 2
|
||||
| Zugvoegel
|
||||
| JaegerinnenDerWeissenMaid
|
||||
| AnhaengerDesGueldenen =>
|
||||
switch (cache.blessedTradition) {
|
||||
| Some((_, _, blessedTradition)) =>
|
||||
hero.liturgicalChants
|
||||
// Filter liturgical chants...
|
||||
|> IM.mapMaybe(
|
||||
fun
|
||||
// ...so that only the active ones remain...
|
||||
| {ActivatableSkill.Dynamic.id, value: Active(_), _} =>
|
||||
// ...and replace with their static entry.
|
||||
IM.lookup(id, staticData.liturgicalChants)
|
||||
| _ => None,
|
||||
)
|
||||
// Check if there is any active entry that does not belong to the
|
||||
// active tradition...
|
||||
|> IM.any(({LiturgicalChant.Static.traditions, _}) =>
|
||||
IS.notElem(blessedTradition.numId, traditions)
|
||||
)
|
||||
// ...which would prohibit removal
|
||||
|> (!)
|
||||
| None => true
|
||||
}
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
let isStyleSpecialAbilityRemovalValid = (hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(specialAbility) =>
|
||||
Activatable_ExtendedStyle.isStyleValidToRemove(hero, specialAbility)
|
||||
};
|
||||
|
||||
/**
|
||||
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
|
||||
* level for an "increase entry", which is an entry that allows to get more
|
||||
* specific values or entries than usually allowed. `currentAmount` is the
|
||||
* current amount of specific values or entries used and `defaultAmount` the
|
||||
* default amount allowed.
|
||||
*/
|
||||
let getMinLevelForIncreaseEntry = (defaultAmount, currentAmount) =>
|
||||
currentAmount > defaultAmount ? Some(currentAmount - defaultAmount) : None;
|
||||
|
||||
/**
|
||||
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
|
||||
* level for a "decrease entry", which is an entry that disallows to get as many
|
||||
* specific values or entries as usually allowed. `currentAmount` is the current
|
||||
* amount of specific values or entries used and `defaultAmount` the default
|
||||
* amount allowed.
|
||||
*/
|
||||
let getMaxLevelForDecreaseEntry = (maxDecrease, current) =>
|
||||
I.max(0, maxDecrease - current);
|
||||
|
||||
let getEntrySpecificMinLevel =
|
||||
(cache, staticData: Static.t, hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Advantage.(
|
||||
switch (fromInt(staticAdvantage.id)) {
|
||||
| LargeSpellSelection =>
|
||||
hero
|
||||
|> ActivatableSkills.countActiveSkillEntries(Spells)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| ZahlreichePredigten =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| ZahlreicheVisionen =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMinLevelForIncreaseEntry(3)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
| Disadvantage(_) => None
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| Imitationszauberei =>
|
||||
hero.spells
|
||||
|> IM.countWith(({ActivatableSkill.Dynamic.id, _}) =>
|
||||
IM.lookup(id, staticData.spells)
|
||||
|> O.option(false, ({Spell.Static.gr, _}) =>
|
||||
gr === Id.Spell.Group.toInt(Spells)
|
||||
)
|
||||
)
|
||||
// Minimum level must be the number of active spells, if there are any
|
||||
|> O.ensure(count => count > 0)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getEntrySpecificMaxLevel = (cache, hero, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => None
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Disadvantage.(
|
||||
switch (fromInt(staticDisadvantage.id)) {
|
||||
| SmallSpellSelection =>
|
||||
hero
|
||||
|> ActivatableSkills.countActiveSkillEntries(Spells)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| WenigePredigten =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| WenigeVisionen =>
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> getMaxLevelForDecreaseEntry(3)
|
||||
|> O.return
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| DunklesAbbildDerBuendnisgabe =>
|
||||
O.Infix.(hero.pact <&> (pact => pact.level))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let adjustMinLevelByDependencies =
|
||||
({Activatable_Dynamic.dependencies, _}, singleEntry, maybeMinLevel) =>
|
||||
L.foldr(
|
||||
(dependency: Activatable_Dynamic.dependency, maybeMinLevel) =>
|
||||
switch (dependency.level) {
|
||||
| Some(definedLevel) =>
|
||||
// get the level dependency from the object and ensure it's
|
||||
// greater than the current minimum level and that...
|
||||
definedLevel > O.sum(maybeMinLevel)
|
||||
// ...if the dependency defines options, too, the entry must match them
|
||||
// as well. A dependency without options is valid for all entries (in
|
||||
// case of calculating a minimum level)
|
||||
&& Dependencies.Activatable.areOptionDependenciesMatched(
|
||||
dependency,
|
||||
Activatable_Convert.singleWithIdToSingle(singleEntry),
|
||||
)
|
||||
? Some(definedLevel) : maybeMinLevel
|
||||
| None => maybeMinLevel
|
||||
},
|
||||
maybeMinLevel,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get minimum valid level.
|
||||
*/
|
||||
let getMinLevel =
|
||||
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) =>
|
||||
getEntrySpecificMinLevel(cache, staticData, hero, staticEntry)
|
||||
|> adjustMinLevelByDependencies(heroEntry, singleEntry);
|
||||
|
||||
let getMaxLevel = (cache, staticData, hero, staticEntry) => {
|
||||
let entrySpecificMaxLevel =
|
||||
getEntrySpecificMaxLevel(cache, hero, staticEntry);
|
||||
|
||||
let maxPossibleWithPrerequisites =
|
||||
Prerequisites.Validation.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry |> Activatable_Accessors.id |> Id.Activatable.toAll,
|
||||
Prerequisites.Activatable.getLevelPrerequisites(staticEntry),
|
||||
);
|
||||
|
||||
O.Infix.(
|
||||
O.liftM2(I.min, entrySpecificMaxLevel, maxPossibleWithPrerequisites)
|
||||
<|> entrySpecificMaxLevel
|
||||
<|> maxPossibleWithPrerequisites
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the given active entry can be removed or changed in level.
|
||||
*/
|
||||
let isRemovalOrModificationValid =
|
||||
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) => {
|
||||
let minLevel =
|
||||
getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry);
|
||||
|
||||
let maxLevel = getMaxLevel(cache, staticData, hero, staticEntry);
|
||||
|
||||
{
|
||||
disabled:
|
||||
O.isSome(minLevel)
|
||||
|| !
|
||||
isNotRequiredByDependencies(
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
)
|
||||
|| !isStyleSpecialAbilityRemovalValid(hero, staticEntry)
|
||||
|| !
|
||||
isEntrySpecificRemovalValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
heroEntry,
|
||||
singleEntry,
|
||||
),
|
||||
maxLevel,
|
||||
minLevel,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
type t = {
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
disabled: bool,
|
||||
};
|
||||
|
||||
/**
|
||||
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
|
||||
* level for an "increase entry", which is an entry that allows to get more
|
||||
* specific values or entries than usually allowed. `currentAmount` is the
|
||||
* current amount of specific values or entries used and `defaultAmount` the
|
||||
* default amount allowed.
|
||||
*/
|
||||
let getMinLevelForIncreaseEntry: (int, int) => option(int);
|
||||
|
||||
/**
|
||||
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
|
||||
* level for a "decrease entry", which is an entry that disallows to get as many
|
||||
* specific values or entries as usually allowed. `currentAmount` is the current
|
||||
* amount of specific values or entries used and `defaultAmount` the default
|
||||
* amount allowed.
|
||||
*/
|
||||
let getMaxLevelForDecreaseEntry: (int, int) => int;
|
||||
|
||||
/**
|
||||
* `isRemovalOrModificationValid cache staticData hero staticEntry heroEntry
|
||||
* singleEntry` checks if the given active entry can be removed or if it's level
|
||||
* can be changed.
|
||||
*/
|
||||
let isRemovalOrModificationValid:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
Activatable_Dynamic.t,
|
||||
Activatable_Convert.singleWithId
|
||||
) =>
|
||||
t;
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* A set of values to validate `Weg der Schreiberin`.
|
||||
*/
|
||||
type matchingLanguagesScripts = {
|
||||
isEntryActiveRequiringMatch: bool,
|
||||
languagesWithMatchingScripts: list(int),
|
||||
scriptsWithMatchingLanguages: list(int),
|
||||
};
|
||||
|
||||
/**
|
||||
* The cache of computed values to optimize performance of activatable list
|
||||
* generation.
|
||||
*/
|
||||
type t = {
|
||||
startExperienceLevel: ExperienceLevel.t,
|
||||
combatStyleCombination: option(Activatable_Dynamic.t),
|
||||
armedCombatStylesCount: int,
|
||||
unarmedCombatStylesCount: int,
|
||||
magicalStylesCount: int,
|
||||
isBlessedStyleActive: bool,
|
||||
magicalStyleCombination: option(Activatable_Dynamic.t),
|
||||
dunklesAbbild: option(Activatable_Dynamic.t),
|
||||
activePactGiftsCount: int,
|
||||
matchingLanguagesScripts,
|
||||
validExtendedSpecialAbilities: list(int),
|
||||
requiredApplyToMagicalActions: bool,
|
||||
specialAbilityPairs:
|
||||
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))),
|
||||
combatTechniquePairs:
|
||||
Ley_IntMap.t((CombatTechnique.Static.t, option(Skill.Dynamic.t))),
|
||||
adventurePoints: AdventurePoints.categories,
|
||||
magicalTraditions: list(Tradition.Magical.fullTradition),
|
||||
blessedTradition: option(Tradition.Blessed.fullTradition),
|
||||
automaticAdvantages: list(int),
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
type single_with_id = {
|
||||
id : int;
|
||||
index : int;
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
(** A single active Activatable. *)
|
||||
|
||||
let single_to_single_with_id (x : 'a Activatable_Dynamic.t) index
|
||||
(s : Activatable_Dynamic.single) =
|
||||
{
|
||||
id = x.id;
|
||||
index;
|
||||
options = s.options;
|
||||
level = s.level;
|
||||
customCost = s.customCost;
|
||||
}
|
||||
|
||||
let hero_entry_to_singles (x : 'a Activatable_Dynamic.t) =
|
||||
x.active |> Ley_List.Index.imap (single_to_single_with_id x)
|
||||
|
||||
let single_with_id_to_single s =
|
||||
{
|
||||
Activatable_Dynamic.options = s.options;
|
||||
level = s.level;
|
||||
customCost = s.customCost;
|
||||
}
|
||||
|
||||
(* (** Converts the object generated by the list item to an object that can be
|
||||
inserted into an array of ActiveObjects.
|
||||
|
||||
@param obj The entry for which you want to convert the object.
|
||||
@param activate The object generated by the list item. *)
|
||||
let convertUIStateToActiveObject =
|
||||
(activate: Record<ActivatableActivationOptions>): Record<ActiveObject> => {
|
||||
const id = AAOA.id (activate)
|
||||
const selectOptionId1 = AAOA.selectOptionId1 (activate)
|
||||
const selectOptionId2 = AAOA.selectOptionId2 (activate)
|
||||
const selectOptionId3 = AAOA.selectOptionId3 (activate)
|
||||
const input = AAOA.input (activate)
|
||||
const level = AAOA.level (activate)
|
||||
const customCost = AAOA.customCost (activate)
|
||||
|
||||
return id === AdvantageId.HatredOf
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === DisadvantageId.PersonalityFlaw
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: or (fmap (elemF (List<number | string> (7, 8)))
|
||||
(selectOptionId1))
|
||||
? input
|
||||
: None,
|
||||
cost: customCost,
|
||||
})
|
||||
: id === SpecialAbilityId.SkillSpecialization
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: alt<number | string> (input)
|
||||
(selectOptionId2),
|
||||
cost: customCost,
|
||||
})
|
||||
: isSome (input) && isSome (selectOptionId1)
|
||||
? ActiveObject ({
|
||||
sid: selectOptionId1,
|
||||
sid2: input,
|
||||
sid3: selectOptionId2,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
: ActiveObject ({
|
||||
sid: alt<number | string> (input)
|
||||
(selectOptionId1),
|
||||
sid2: then (guard (isSome (input) || isSome (selectOptionId1)))
|
||||
(selectOptionId2),
|
||||
sid3: selectOptionId3,
|
||||
tier: level,
|
||||
cost: customCost,
|
||||
})
|
||||
} *)
|
||||
|
||||
(* /**
|
||||
* Generates a list of ActiveObjects based on the given instance.
|
||||
*/
|
||||
export const convertActivatableToArray =
|
||||
(x: Record<ActivatableDependent>) =>
|
||||
pipe_ (
|
||||
x,
|
||||
ADA.active,
|
||||
imap (index => toActiveObjectWithId (index) (ADA.id (x)))
|
||||
) *)
|
||||
(* /**
|
||||
* Get all active items in an array.
|
||||
* @param state A state slice.
|
||||
*/
|
||||
export const getActiveFromState =
|
||||
foldr (pipe (convertActivatableToArray, append)) (empty)
|
||||
|
||||
export interface ActiveObjectAny extends ActiveObject {
|
||||
[key: string]: any
|
||||
} *)
|
||||
|
||||
let activatable_option_to_select_option_id = function
|
||||
| Id.Activatable.Option.Preset id -> Some id
|
||||
| CustomInput _ -> None
|
||||
@@ -0,0 +1,29 @@
|
||||
(** Convert specific values to be able to easier work with them.
|
||||
|
||||
This function provides some utility functions to convert some types from the
|
||||
state into smaller chuncks that are more usable for working with single
|
||||
entries. *)
|
||||
|
||||
type single_with_id = {
|
||||
id : int;
|
||||
index : int;
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
(** A single active Activatable. *)
|
||||
|
||||
val hero_entry_to_singles : 'a Activatable_Dynamic.t -> single_with_id list
|
||||
(** Converts an Activatable hero entry into a "flattened" list of it's
|
||||
activations. *)
|
||||
|
||||
val single_to_single_with_id :
|
||||
'a Activatable_Dynamic.t ->
|
||||
int ->
|
||||
Activatable_Dynamic.single ->
|
||||
single_with_id
|
||||
|
||||
val activatable_option_to_select_option_id :
|
||||
Id.Activatable.Option.t -> Id.Activatable.SelectOption.t option
|
||||
|
||||
val single_with_id_to_single : single_with_id -> Activatable_Dynamic.single
|
||||
@@ -0,0 +1,64 @@
|
||||
type single = {
|
||||
options : Id.Activatable.Option.t list;
|
||||
level : int option;
|
||||
customCost : int option;
|
||||
}
|
||||
|
||||
type dependency = {
|
||||
source : Id.Activatable.t;
|
||||
target : int OneOrMany.t;
|
||||
active : bool;
|
||||
options : Id.Activatable.SelectOption.t OneOrMany.t list;
|
||||
level : int option;
|
||||
}
|
||||
|
||||
type 'static t = {
|
||||
id : int;
|
||||
active : single list;
|
||||
dependencies : dependency list;
|
||||
static : 'static option;
|
||||
}
|
||||
|
||||
let empty static id = { id; active = []; dependencies = []; static }
|
||||
|
||||
let is_empty (x : 'a t) = Ley_List.null x.active && Ley_List.null x.dependencies
|
||||
|
||||
let is_active (x : 'a t) = Ley_List.Extra.notNull x.active
|
||||
|
||||
let is_active' x = Ley_Option.option false is_active x
|
||||
|
||||
module type S = sig
|
||||
type static
|
||||
|
||||
type nonrec t = static t
|
||||
(** The dynamic values in a character with a reference to the static values
|
||||
from the database. *)
|
||||
|
||||
val empty : static option -> int -> t
|
||||
(** [empty id] creates a new dynamic entry from an id. *)
|
||||
|
||||
val is_empty : t -> bool
|
||||
(** [is_empty x] checks if the passed dynamic entry is empty. *)
|
||||
|
||||
val is_active : t -> bool
|
||||
(** [is_active x] checks if the passed dynamic entry is active. *)
|
||||
|
||||
val is_active' : t option -> bool
|
||||
(** [is_active' x] checks if the passed optional dynamic entry is active. *)
|
||||
end
|
||||
|
||||
module Make (S : sig
|
||||
type static
|
||||
end) : S with type static = S.static = struct
|
||||
type static = S.static
|
||||
|
||||
type nonrec t = static t
|
||||
|
||||
let empty = empty
|
||||
|
||||
let is_empty = is_empty
|
||||
|
||||
let is_active = is_active
|
||||
|
||||
let is_active' = is_active'
|
||||
end
|
||||
@@ -0,0 +1,398 @@
|
||||
open Ley_Option;
|
||||
|
||||
/**
|
||||
* Returns a getter and a setter for the appropiate dependency list for the
|
||||
* passed style special ability or extended special ability.
|
||||
*/
|
||||
let getStyleDependenciesAcc = (style: SpecialAbility.Static.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.Group.fromInt(style.gr)) {
|
||||
| CombatStylesArmed
|
||||
| CombatStylesUnarmed
|
||||
| CombatExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.combatStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
combatStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| MagicalStyles
|
||||
| MagicalExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.magicalStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
magicalStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| BlessedStyles
|
||||
| KarmaExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.blessedStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
blessedStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| SkillStyles
|
||||
| SkillExtended =>
|
||||
Some((
|
||||
((hero: Hero.t) => hero.skillStyleDependencies),
|
||||
(
|
||||
(hero: Hero.t, dependencies) => {
|
||||
...hero,
|
||||
skillStyleDependencies: dependencies,
|
||||
}
|
||||
),
|
||||
))
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* If a style has multiple possible extended special abilities in one slot,
|
||||
* it has more options to offer. If such a slot is used and a new style
|
||||
* dependency with the used slot, but not multiple possibilities, is added,
|
||||
* this function will shift the use to the style dependency with the fixed
|
||||
* option instead, so there are more options left in the end.
|
||||
*/
|
||||
let moveActiveInListToNew = (newxs, x: Hero.styleDependency) =>
|
||||
switch (x.id, x.active) {
|
||||
// If the dependency has got a list of possible ids and is used, we check
|
||||
// if the used id is included in the new dependencies as a dependency
|
||||
// without a list of options to make more special abilities possible
|
||||
| (Many(_), Some(active)) =>
|
||||
// If a Some, it is the index in the list of new dependencies where we can
|
||||
// put the active dependency instead
|
||||
let index =
|
||||
Ley_List.findIndex(
|
||||
(newx: Hero.styleDependency) =>
|
||||
switch (newx.id) {
|
||||
| One(id) => id === active
|
||||
| Many(_) => false
|
||||
},
|
||||
newxs,
|
||||
);
|
||||
|
||||
switch (index) {
|
||||
| Some(index) => (
|
||||
Ley_List.Index.modifyAt(
|
||||
index,
|
||||
(newx: Hero.styleDependency) => {...newx, active: Some(active)},
|
||||
newxs,
|
||||
),
|
||||
{...x, active: None},
|
||||
)
|
||||
| None => (newxs, x)
|
||||
};
|
||||
| (Many(_), None)
|
||||
| (One(_), _) => (newxs, x)
|
||||
};
|
||||
|
||||
/**
|
||||
* `generateStyleDependencies heroSpecialAbilities styleSpecialAbility`
|
||||
* returns generated style dependencies for a style special ability.
|
||||
*/
|
||||
let generateStyleDependencies =
|
||||
(heroSpecialAbilities, styleSpecialAbility: SpecialAbility.Static.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (styleSpecialAbility.groupSpecific) {
|
||||
| CombatStyle({extended: (ext1, ext2, ext3), _})
|
||||
| GeneralStyle({extended: (ext1, ext2, ext3), _}) =>
|
||||
Ley_Option.Infix.(
|
||||
[ext1, ext2, ext3]
|
||||
|> Ley_List.map(({SpecialAbility.Static.Extended.id, _}) =>
|
||||
(
|
||||
{id: One(id), active: None, origin: styleSpecialAbility.id}: Hero.styleDependency
|
||||
)
|
||||
)
|
||||
|> (
|
||||
xs =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(styleSpecialAbility.id)) {
|
||||
// For this style, the user must choose between two special
|
||||
// abilities to be an extended special ability.
|
||||
| ScholarDesMagierkollegsZuHoningen =>
|
||||
Ley_IntMap.lookup(
|
||||
styleSpecialAbility.id,
|
||||
heroSpecialAbilities,
|
||||
)
|
||||
>>= (
|
||||
(x: Activatable_Dynamic.t) =>
|
||||
x.active |> Ley_Option.listToOption
|
||||
)
|
||||
>>= (x => Ley_List.Safe.atMay(x.options, 1))
|
||||
|> (
|
||||
fun
|
||||
| Some(Preset(SpecialAbility(id))) => [
|
||||
{
|
||||
Hero.id: One(id),
|
||||
active: None,
|
||||
origin: styleSpecialAbility.id,
|
||||
},
|
||||
...xs,
|
||||
]
|
||||
| Some(_)
|
||||
| None => xs
|
||||
)
|
||||
| _ => xs
|
||||
}
|
||||
)
|
||||
)
|
||||
|> Ley_Option.return
|
||||
)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability.
|
||||
*/
|
||||
let addStyleExtendedSpecialAbilityDependencies = (styleSpecialAbility, hero) =>
|
||||
liftM2(
|
||||
((get, set), newxs) =>
|
||||
hero
|
||||
|> get
|
||||
|> Ley_List.mapAccumL(moveActiveInListToNew, newxs)
|
||||
|> (((xs1, xs2)) => xs1 @ xs2)
|
||||
|> set(hero),
|
||||
getStyleDependenciesAcc(styleSpecialAbility),
|
||||
generateStyleDependencies(hero.specialAbilities, styleSpecialAbility),
|
||||
)
|
||||
|> Ley_Option.fromOption(hero);
|
||||
|
||||
/**
|
||||
* Get the index in the list of style dependencies where there is a free slot
|
||||
* for the passed extended special ability. Prefers fixed ID slots.
|
||||
*/
|
||||
let getIndexOfFreeDependency =
|
||||
(
|
||||
extendedSpecialAbility: SpecialAbility.Static.t,
|
||||
xs: list(Hero.styleDependency),
|
||||
) =>
|
||||
Ley_Option.Infix.
|
||||
// Prefer dependency with fixed option
|
||||
(
|
||||
Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.id) {
|
||||
| One(id) => id === extendedSpecialAbility.id
|
||||
| Many(_) => false
|
||||
},
|
||||
xs,
|
||||
)
|
||||
// Otherwise search for dependency with dynamic option
|
||||
<|> Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.id) {
|
||||
| One(_) => false
|
||||
| Many(ids) => Ley_List.elem(extendedSpecialAbility.id, ids)
|
||||
},
|
||||
xs,
|
||||
)
|
||||
|> Ley_Option.fromOption(-1)
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds the passed extended special ability to a free slot of a style
|
||||
* dependency.
|
||||
*/
|
||||
let addExtendedSpecialAbilityDependency =
|
||||
(extendedSpecialAbility, hero: Hero.t) =>
|
||||
extendedSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> (
|
||||
xs =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getIndexOfFreeDependency(extendedSpecialAbility, xs),
|
||||
(d: Hero.styleDependency) =>
|
||||
{...d, active: Some(extendedSpecialAbility.id)},
|
||||
xs,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability or adds the passed special ability's ID to a free slot of a
|
||||
* style dependency if it is an extended special ability.
|
||||
*/
|
||||
let addAllStyleRelatedDependencies = (specialAbility, hero) =>
|
||||
hero
|
||||
|> addStyleExtendedSpecialAbilityDependencies(specialAbility)
|
||||
|> addExtendedSpecialAbilityDependency(specialAbility);
|
||||
|
||||
/**
|
||||
* `getSplittedRemainingAndToRemove` takes a style special ability's ID and
|
||||
* the list of style dependencies and returns `(toBeRemoved, remaining)`,
|
||||
* where `toBeRemoved` is the list of style dependencies that have to be
|
||||
* removed when removing the style special ability and `remaining` is the list
|
||||
* of remaining style dependencies after the removal.
|
||||
*/
|
||||
let getSplittedRemainingAndToRemove = (id, xs) =>
|
||||
Ley_List.partition((x: Hero.styleDependency) => x.origin === id, xs);
|
||||
|
||||
/**
|
||||
* Returns an index of a free style dependency so that the currently used
|
||||
* style dependency can be removed and the use can be moved.
|
||||
*/
|
||||
let getAlternativeIndex = (toBeRemoved: Hero.styleDependency, remainings) =>
|
||||
switch (toBeRemoved.active) {
|
||||
| Some(activeId) =>
|
||||
remainings
|
||||
|> Ley_List.findIndex((remaining: Hero.styleDependency) =>
|
||||
(
|
||||
switch (remaining.id) {
|
||||
| One(id) => id === activeId
|
||||
| Many(ids) => Ley_List.elem(activeId, ids)
|
||||
}
|
||||
)
|
||||
&& Ley_Option.isNone(remaining.active)
|
||||
)
|
||||
|> Ley_Option.fromOption(-1)
|
||||
| None => (-1)
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability.
|
||||
*/
|
||||
let removeStyleExtendedSpecialAbilityDependencies =
|
||||
(styleSpecialAbility, hero) =>
|
||||
styleSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|
||||
|> (
|
||||
((dependenciesToRemove, remainingDependencies)) =>
|
||||
dependenciesToRemove
|
||||
|> Ley_List.filter((x: Hero.styleDependency) =>
|
||||
Ley_Option.isSome(x.active)
|
||||
)
|
||||
|> Ley_List.foldr(
|
||||
(toRemove: Hero.styleDependency, remainings) =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getAlternativeIndex(toRemove, remainings),
|
||||
(remaining: Hero.styleDependency) =>
|
||||
{...remaining, active: toRemove.active},
|
||||
remainings,
|
||||
),
|
||||
remainingDependencies,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the index in the list of style dependencies where there is a used slot
|
||||
* for the passed extended special ability.
|
||||
*/
|
||||
let getIndexOfUsedDependency =
|
||||
(
|
||||
extendedSpecialAbility: SpecialAbility.Static.t,
|
||||
xs: list(Hero.styleDependency),
|
||||
) =>
|
||||
Ley_List.findIndex(
|
||||
(x: Hero.styleDependency) =>
|
||||
switch (x.active) {
|
||||
| Some(id) => id === extendedSpecialAbility.id
|
||||
| None => false
|
||||
},
|
||||
xs,
|
||||
)
|
||||
|> Ley_Option.fromOption(-1);
|
||||
|
||||
/**
|
||||
* Removes the passed extended special ability from a used slot of a style
|
||||
* dependency.
|
||||
*/
|
||||
let removeExtendedSpecialAbilityDependency =
|
||||
(extendedSpecialAbility, hero: Hero.t) =>
|
||||
extendedSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(hero, ((get, set)) =>
|
||||
hero
|
||||
|> get
|
||||
|> (
|
||||
xs =>
|
||||
Ley_List.Index.modifyAt(
|
||||
getIndexOfUsedDependency(extendedSpecialAbility, xs),
|
||||
(d: Hero.styleDependency) => {...d, active: None},
|
||||
xs,
|
||||
)
|
||||
|> set(hero)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability or removes the passed special ability's ID from a
|
||||
* used slot of a style dependency if it is an extended special ability.
|
||||
*/
|
||||
let removeAllStyleRelatedDependencies = (specialAbility, hero) =>
|
||||
hero
|
||||
|> removeStyleExtendedSpecialAbilityDependencies(specialAbility)
|
||||
|> removeExtendedSpecialAbilityDependency(specialAbility);
|
||||
|
||||
/**
|
||||
* Return flat array of available extended special abilities' IDs.
|
||||
*/
|
||||
let getAvailableExtendedSpecialAbilities = styleDependencies =>
|
||||
styleDependencies
|
||||
|> Ley_List.concatMap((x: Hero.styleDependency) =>
|
||||
switch (x.active, x.id) {
|
||||
| (None, One(id)) => [id]
|
||||
| (None, Many(ids)) => ids
|
||||
| (Some(_), _) => []
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculates a list of available Extended Special Abilties. The availability is
|
||||
* only based on bought Style Special Abilities, so (other) prerequisites have
|
||||
* to be checked separately.
|
||||
*/
|
||||
let getAllAvailableExtendedSpecialAbilities =
|
||||
Ley_List.concatMap(getAvailableExtendedSpecialAbilities);
|
||||
|
||||
/**
|
||||
* Checks if the passed special ability is a style and if it is valid to
|
||||
* remove based on registered active extended special abilities.
|
||||
*/
|
||||
let isStyleValidToRemove = (hero, styleSpecialAbility) =>
|
||||
styleSpecialAbility
|
||||
|> getStyleDependenciesAcc
|
||||
|> Ley_Option.option(true, ((get, _)) =>
|
||||
hero
|
||||
|> get
|
||||
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|
||||
|> (
|
||||
((dependenciesToRemove, remainingDependencies)) =>
|
||||
dependenciesToRemove
|
||||
// Check for every used dependency slot...
|
||||
|> Ley_List.filter((x: Hero.styleDependency) =>
|
||||
Ley_Option.isSome(x.active)
|
||||
)
|
||||
// ...if it can be moved to a different dependency
|
||||
|> Ley_List.all(x =>
|
||||
getAlternativeIndex(x, remainingDependencies) > (-1)
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Working with Style Special Abilties and their Extended Special Abilties.
|
||||
*
|
||||
* This module provides some functions for working with Style and Extended
|
||||
* Special Abilties, how to manage their dependencies and what is allowed to do
|
||||
* in that context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds extended special ability dependencies if the passed entry is a style
|
||||
* special ability or adds the passed special ability's ID to a free slot of a
|
||||
* style dependency if it is an extended special ability.
|
||||
*/
|
||||
let addAllStyleRelatedDependencies:
|
||||
(SpecialAbility.Static.t, Hero.t) => Hero.t;
|
||||
|
||||
/**
|
||||
* Removes extended special ability dependencies if the passed entry is a
|
||||
* style special ability or removes the passed special ability's ID from a
|
||||
* used slot of a style dependency if it is an extended special ability.
|
||||
*/
|
||||
let removeAllStyleRelatedDependencies:
|
||||
(SpecialAbility.Static.t, Hero.t) => Hero.t;
|
||||
|
||||
/**
|
||||
* Calculates a list of available Extended Special Abilties. The availability is
|
||||
* only based on bought Style Special Abilities, so (other) prerequisites have
|
||||
* to be checked separately.
|
||||
*/
|
||||
let getAllAvailableExtendedSpecialAbilities:
|
||||
list(list(Hero.styleDependency)) => list(int);
|
||||
|
||||
/**
|
||||
* Checks if the passed special ability is a style and if it is valid to
|
||||
* remove based on registered active extended special abilities.
|
||||
*/
|
||||
let isStyleValidToRemove: (Hero.t, SpecialAbility.Static.t) => bool;
|
||||
@@ -0,0 +1,313 @@
|
||||
module IM = Ley_IntMap;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
type t = {
|
||||
id: int,
|
||||
name: string,
|
||||
apValue: option(OneOrMany.t(int)),
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
selectOptions: list(SelectOption.t),
|
||||
heroEntry: option(Activatable_Dynamic.t),
|
||||
staticEntry: Activatable.t,
|
||||
customCostDisabled: bool,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
type result =
|
||||
| Valid(t)
|
||||
| Invalid(Activatable.t);
|
||||
|
||||
let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) =>
|
||||
Activatable_Modifier.modifyByLevel(
|
||||
3,
|
||||
IM.lookup(Id.Advantage.toInt(advantageId), hero.advantages),
|
||||
IM.lookup(Id.Disadvantage.toInt(disadvantageId), hero.disadvantages),
|
||||
);
|
||||
|
||||
let modifyOtherOptions =
|
||||
(cache, staticData: Static.t, hero, staticEntry, maybeHeroEntry, base) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_) => Some(base)
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
[@warning "-4"]
|
||||
Id.Disadvantage.(
|
||||
switch (fromInt(staticDisadvantage.id)) {
|
||||
| WenigePredigten as id
|
||||
| WenigeVisionen as id =>
|
||||
Some({
|
||||
...base,
|
||||
maxLevel:
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
switch (id) {
|
||||
| WenigePredigten => Predigten
|
||||
| _ => Visionen
|
||||
},
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|
||||
|> O.ensure((<)(0)),
|
||||
})
|
||||
|
||||
| SmallSpellSelection =>
|
||||
Some({
|
||||
...base,
|
||||
maxLevel:
|
||||
ActivatableSkills.countActiveSkillEntries(Spells, hero)
|
||||
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|
||||
|> O.ensure((<)(0)),
|
||||
})
|
||||
|
||||
| _ => Some(base)
|
||||
}
|
||||
)
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(staticSpecialAbility.id)) {
|
||||
| CraftInstruments =>
|
||||
let getSkillValue = skill =>
|
||||
hero.skills
|
||||
|> IM.lookup(Id.Skill.toInt(skill))
|
||||
|> Skill.Dynamic.getValueDef;
|
||||
|
||||
// Sum of SR of Woodworking and Metalworking must be at least 12
|
||||
getSkillValue(Woodworking) + getSkillValue(Metalworking) >= 12
|
||||
? Some(base) : None;
|
||||
|
||||
| Hunter =>
|
||||
EntryGroups.CombatTechnique.getFromGroup(
|
||||
Ranged,
|
||||
cache.combatTechniquePairs,
|
||||
)
|
||||
|> IM.any(
|
||||
fun
|
||||
// At least one ranged combat technique must be on CtR 10 or higher
|
||||
| (_, Some((heroEntry: Skill.Dynamic.t))) =>
|
||||
heroEntry.value >= 10
|
||||
| _ => false,
|
||||
)
|
||||
? Some(base) : None
|
||||
|
||||
| TraditionGuildMages
|
||||
| TraditionWitches
|
||||
| TraditionElves
|
||||
| TraditionDruids
|
||||
| TraditionIllusionist
|
||||
| TraditionQabalyaMage
|
||||
| TraditionGeoden
|
||||
| TraditionZauberalchimisten
|
||||
| TraditionSchelme
|
||||
| TraditionBrobimGeoden =>
|
||||
// Only one tradition allowed
|
||||
L.null(cache.magicalTraditions) ? Some(base) : None
|
||||
|
||||
| PropertyKnowledge
|
||||
| AspectKnowledge =>
|
||||
switch (staticSpecialAbility.apValue) {
|
||||
| Some(PerLevel(values)) =>
|
||||
let index =
|
||||
O.option(
|
||||
0,
|
||||
({active, _}: Activatable_Dynamic.t) => active |> L.length,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
O.Infix.(
|
||||
L.Safe.atMay(values, index)
|
||||
<&> (apValue => {...base, apValue: Some(One(apValue))})
|
||||
);
|
||||
| Some(Flat(_))
|
||||
| None => None
|
||||
}
|
||||
|
||||
| TraditionChurchOfPraios
|
||||
| TraditionChurchOfRondra
|
||||
| TraditionChurchOfBoron
|
||||
| TraditionChurchOfHesinde
|
||||
| TraditionChurchOfPhex
|
||||
| TraditionChurchOfPeraine
|
||||
| TraditionChurchOfEfferd
|
||||
| TraditionChurchOfTravia
|
||||
| TraditionChurchOfFirun
|
||||
| TraditionChurchOfTsa
|
||||
| TraditionChurchOfIngerimm
|
||||
| TraditionChurchOfRahja
|
||||
| TraditionCultOfTheNamelessOne
|
||||
| TraditionChurchOfAves
|
||||
| TraditionChurchOfIfirn
|
||||
| TraditionChurchOfKor
|
||||
| TraditionChurchOfNandus
|
||||
| TraditionChurchOfSwafnir
|
||||
| TraditionCultOfNuminoru =>
|
||||
// Only one tradition allowed
|
||||
O.isNone(cache.blessedTradition) ? Some(base) : None
|
||||
|
||||
| Recherchegespuer =>
|
||||
O.Infix.(
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(Wissensdurst))
|
||||
<&> (({active, _}) => active)
|
||||
>>= O.listToOption
|
||||
>>= (({options, _}) => O.listToOption(options))
|
||||
>>= (
|
||||
fun
|
||||
| Preset(Skill(skillId)) =>
|
||||
IM.lookup(skillId, staticData.skills)
|
||||
| _ => None
|
||||
)
|
||||
>>= (
|
||||
({ic, _}) =>
|
||||
switch (staticSpecialAbility.apValue) {
|
||||
| Some(PerLevel(values)) =>
|
||||
values
|
||||
|> L.map((+)(IC.getApForActivatation(ic)))
|
||||
|> (
|
||||
sumValues =>
|
||||
{...base, apValue: Some(Many(sumValues))} |> O.return
|
||||
)
|
||||
| Some(Flat(_))
|
||||
| None => None
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
| PredigtDerGemeinschaft
|
||||
| PredigtDerZuversicht
|
||||
| PredigtDesGottvertrauens
|
||||
| PredigtDesWohlgefallens
|
||||
| PredigtWiderMissgeschicke =>
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|
||||
|> Activatable_Accessors.isActiveM;
|
||||
|
||||
let max =
|
||||
getSermonOrVisionCountMax(
|
||||
hero,
|
||||
ZahlreichePredigten,
|
||||
WenigePredigten,
|
||||
);
|
||||
|
||||
let isLessThanMax =
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Predigten,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
< max;
|
||||
|
||||
isAdvActive(Prediger) && isLessThanMax || isAdvActive(Blessed)
|
||||
? Some(base) : None;
|
||||
|
||||
| VisionDerBestimmung
|
||||
| VisionDerEntrueckung
|
||||
| VisionDerGottheit
|
||||
| VisionDesSchicksals
|
||||
| VisionDesWahrenGlaubens =>
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|
||||
|> Activatable_Accessors.isActiveM;
|
||||
|
||||
let max =
|
||||
getSermonOrVisionCountMax(hero, ZahlreicheVisionen, WenigeVisionen);
|
||||
|
||||
let isLessThanMax =
|
||||
EntryGroups.SpecialAbility.countActiveFromGroup(
|
||||
Visionen,
|
||||
cache.specialAbilityPairs,
|
||||
)
|
||||
< max;
|
||||
|
||||
isAdvActive(Visionaer) && isLessThanMax || isAdvActive(Blessed)
|
||||
? Some(base) : None;
|
||||
|
||||
| DunklesAbbildDerBuendnisgabe =>
|
||||
O.Infix.(
|
||||
hero.pact
|
||||
>>= (
|
||||
({level, _}) =>
|
||||
level > 0 ? Some({...base, maxLevel: Some(level)}) : None
|
||||
)
|
||||
)
|
||||
|
||||
| TraditionArcaneBard
|
||||
| TraditionArcaneDancer
|
||||
| TraditionIntuitiveMage
|
||||
| TraditionSavant
|
||||
| TraditionAnimisten =>
|
||||
cache.adventurePoints.spentOnMagicalAdvantages <= 25
|
||||
&& cache.adventurePoints.spentOnMagicalDisadvantages <= 25
|
||||
&& L.null(cache.magicalTraditions)
|
||||
? Some(base) : None
|
||||
|
||||
| _ => Some(base)
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let getInactive = (cache, staticData, hero, staticEntry, maybeHeroEntry) => {
|
||||
let maxLevel =
|
||||
Activatable_Inactive_Validation.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
let isAdditionValid =
|
||||
Activatable_Inactive_Validation.isAdditionValid(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
maxLevel,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
if (isAdditionValid) {
|
||||
O.Infix.(
|
||||
Activatable_Inactive_SelectOptions.getAvailableSelectOptions(
|
||||
staticData,
|
||||
hero,
|
||||
cache.magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
)
|
||||
<&> (
|
||||
selectOptions => {
|
||||
id: Activatable_Accessors.id'(staticEntry),
|
||||
name: Activatable_Accessors.name(staticEntry),
|
||||
apValue: Activatable_Accessors.apValue'(staticEntry),
|
||||
minLevel: None,
|
||||
maxLevel,
|
||||
selectOptions,
|
||||
heroEntry: maybeHeroEntry,
|
||||
staticEntry,
|
||||
customCostDisabled: true,
|
||||
isAutomatic:
|
||||
switch (staticEntry) {
|
||||
| Advantage({id, _}) => L.elem(id, cache.automaticAdvantages)
|
||||
| Disadvantage(_)
|
||||
| SpecialAbility(_) => false
|
||||
},
|
||||
}
|
||||
)
|
||||
>>= (
|
||||
base =>
|
||||
modifyOtherOptions(
|
||||
cache,
|
||||
staticData,
|
||||
hero,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
base,
|
||||
)
|
||||
)
|
||||
|> O.option(Invalid(staticEntry), base => Valid(base))
|
||||
);
|
||||
} else {
|
||||
Invalid(staticEntry);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
type t = {
|
||||
id: int,
|
||||
name: string,
|
||||
apValue: option(OneOrMany.t(int)),
|
||||
minLevel: option(int),
|
||||
maxLevel: option(int),
|
||||
selectOptions: list(SelectOption.t),
|
||||
heroEntry: option(Activatable_Dynamic.t),
|
||||
staticEntry: Activatable.t,
|
||||
customCostDisabled: bool,
|
||||
isAutomatic: bool,
|
||||
};
|
||||
|
||||
type result =
|
||||
| Valid(t)
|
||||
| Invalid(Activatable.t);
|
||||
|
||||
/**
|
||||
* `getInactive cache staticData hero staticEntry maybeHeroEntry` validates an
|
||||
* inactive entry, filters possible options and then either returns a valid
|
||||
* entry with additional and filtered options or an invalid entry.
|
||||
*
|
||||
* This can be used to display valid as well as invalid entries in a single
|
||||
* list.
|
||||
*/
|
||||
let getInactive:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
result;
|
||||
@@ -0,0 +1,699 @@
|
||||
open SelectOption;
|
||||
open Ley_Option;
|
||||
open Activatable_Accessors;
|
||||
open Activatable_SelectOptions;
|
||||
|
||||
module F = Ley_Function;
|
||||
module SO = SelectOption;
|
||||
module SOM = SelectOption.Map;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
module T = Ley_Transducer;
|
||||
module IM = Ley_IntMap;
|
||||
module IS = Ley_IntSet;
|
||||
|
||||
open T;
|
||||
|
||||
/**
|
||||
* Test if the id of the passed select option is activated for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
let isNotActive = maybeHeroEntry =>
|
||||
maybeHeroEntry
|
||||
|> O.option([], getActiveSelectOptions1)
|
||||
|> ((activeSelections, {id, _}: SO.t) => L.notElem(id, activeSelections));
|
||||
|
||||
/**
|
||||
* Test if a select option is not activated more than once for the passed
|
||||
* `ActivatableDependent`.
|
||||
*/
|
||||
let areNoSameActive = maybeHeroEntry =>
|
||||
maybeHeroEntry
|
||||
|> O.option([], getActiveSelectOptions1)
|
||||
|> (
|
||||
(activeSelections, {id, _}: SO.t) => L.countMax(id, 1, activeSelections)
|
||||
);
|
||||
|
||||
/**
|
||||
* `isNotRequired otherActivatables maybeHeroEntry so` returns if the id of the
|
||||
* passed select option `so` is required for the passed Activatable
|
||||
* `maybeHeroEntry`. `otherActivatables` should be a map of the Activatables
|
||||
* from the same group ()
|
||||
*/
|
||||
let isNotRequired = (otherActivatables, maybeHeroEntry) =>
|
||||
maybeHeroEntry
|
||||
|> O.option(
|
||||
[],
|
||||
Dependencies.Flatten.getRequiredSelectOptions1(otherActivatables),
|
||||
)
|
||||
|> (
|
||||
(applicableOptions, {id, _}: SO.t) =>
|
||||
L.all(
|
||||
fun
|
||||
| OneOrMany.One(option) => Id.Activatable.SelectOption.(option != id)
|
||||
| Many(options) => L.notElem(id, options),
|
||||
applicableOptions,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* `isNotRequiredNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
|
||||
*
|
||||
* Test if the id of the passed select option is neither required nor activated
|
||||
* for the passed `ActivatableDependent`.
|
||||
*/
|
||||
let isNotRequiredNotActive = (otherActivatables, maybeHeroEntry) => {
|
||||
let isNoActiveSelection = isNotActive(maybeHeroEntry);
|
||||
let isNoRequiredSelection =
|
||||
isNotRequired(otherActivatables, maybeHeroEntry);
|
||||
|
||||
so => isNoActiveSelection(so) && isNoRequiredSelection(so);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available and if their
|
||||
* prerequisites are met.
|
||||
*/
|
||||
let isValid = (staticData: Static.t, hero: Hero.t, entryId, fold) =>
|
||||
fold
|
||||
>>~ EntryAvailability.isAvailableNull(
|
||||
(x: SelectOption.t) => x.src,
|
||||
staticData.publications,
|
||||
hero.rules,
|
||||
)
|
||||
>>~ (
|
||||
x =>
|
||||
x.prerequisites
|
||||
|> Prerequisite.Collection.ByLevel.getFirstLevel
|
||||
|> Ley_List.map(Prerequisite.General.unify)
|
||||
|> Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
entryId,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available, if their
|
||||
* prerequisites are met, and if its neither active nor required.
|
||||
*/
|
||||
let isNoGenericRequiredOrActiveSelection =
|
||||
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
|
||||
fold
|
||||
|> isValid(staticData, hero, entryId)
|
||||
>>~ isNotRequiredNotActive(otherActivatables, maybeHeroEntry);
|
||||
|
||||
/**
|
||||
* Returns a transducer to check if an item is available, if their
|
||||
* prerequisites are met, and if its not required.
|
||||
*/
|
||||
let isNoGenericRequiredSelection =
|
||||
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
|
||||
fold
|
||||
|> isValid(staticData, hero, entryId)
|
||||
>>~ isNotRequired(otherActivatables, maybeHeroEntry);
|
||||
|
||||
/**
|
||||
* From a list of spell select options, only return the ones that match the
|
||||
* predicate.
|
||||
*/
|
||||
let filterSpellSelectOptions = (pred, fold) =>
|
||||
fold
|
||||
>>~ (
|
||||
(x: SelectOption.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (x.staticEntry) {
|
||||
| Some(Spell(spell)) => Some(spell)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
|> O.option(false, pred)
|
||||
);
|
||||
|
||||
let getAvailableSelectOptionsTransducer =
|
||||
(
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
) => {
|
||||
// Prefill some parameters
|
||||
let isValidShort = isValid(staticData, hero);
|
||||
|
||||
// Prefill some parameters
|
||||
let isNoGenericRequiredOrActiveSelectionShort =
|
||||
isNoGenericRequiredOrActiveSelection(staticData, hero, maybeHeroEntry);
|
||||
|
||||
// Prefill some parameters
|
||||
let isNoGenericRequiredSelectionShort =
|
||||
isNoGenericRequiredSelection(staticData, hero, maybeHeroEntry);
|
||||
|
||||
switch ((staticEntry: Activatable.t)) {
|
||||
| Advantage(staticAdvantage) =>
|
||||
let id = Id.All.Advantage(staticAdvantage.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.advantages, id);
|
||||
|
||||
let isNoRequiredSelection =
|
||||
isNoGenericRequiredSelectionShort(hero.advantages, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Advantage.fromInt(staticAdvantage.id)) {
|
||||
| ExceptionalSkill =>
|
||||
Some(
|
||||
fold =>
|
||||
// There shouldnt be more than two activations for the same skill
|
||||
fold |> isNoRequiredSelection >>~ areNoSameActive(maybeHeroEntry),
|
||||
)
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
let id = Id.All.Disadvantage(staticDisadvantage.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.disadvantages, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.Disadvantage.fromInt(staticDisadvantage.id)) {
|
||||
| PersonalityFlaw =>
|
||||
// List of unique active select options
|
||||
let uniqueSelections =
|
||||
O.option(
|
||||
[],
|
||||
heroEntry =>
|
||||
heroEntry
|
||||
|> mapActiveOptions1(
|
||||
fun
|
||||
| Preset(Generic(id)) => Some(id)
|
||||
| _ => None,
|
||||
),
|
||||
maybeHeroEntry,
|
||||
);
|
||||
|
||||
// Checks if the passed id either matches the passed select option's id
|
||||
// and is already active.
|
||||
let isInfiniteActive = (id, selectOption: SO.t) =>
|
||||
selectOption.id == Generic(id) && L.elem(id, uniqueSelections);
|
||||
|
||||
// Checks if the passed select option is Prejudice and if Prejudice is
|
||||
// already active.
|
||||
let isPrejudiceAndActive = isInfiniteActive(7);
|
||||
|
||||
// Checks if the passed select option is Unworldly and if Unworldly is
|
||||
// already active.
|
||||
let isUnworldlyAndActive = isInfiniteActive(8);
|
||||
|
||||
// Checks if the passed select option is not active, not required and if
|
||||
// there is a maximum of one other unique select option of this entry
|
||||
// active.
|
||||
let isNotActiveAndMaxNotReached = selectOption =>
|
||||
isNotRequiredNotActive(
|
||||
hero.disadvantages,
|
||||
maybeHeroEntry,
|
||||
selectOption,
|
||||
)
|
||||
&& L.lengthMax(1, uniqueSelections);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isValidShort(Disadvantage(staticDisadvantage.id))
|
||||
>>~ (
|
||||
x =>
|
||||
// If Prejudice is active, it can be activated multiple times
|
||||
isPrejudiceAndActive(x)
|
||||
// If Unworldly is active, it can be activated multiple times
|
||||
|| isUnworldlyAndActive(x)
|
||||
// If the select option has not been activated yet, there must be
|
||||
// a maximum of one other unique activations, since only a maximum
|
||||
// of two unique is allowed
|
||||
|| isNotActiveAndMaxNotReached(x)
|
||||
),
|
||||
);
|
||||
| NegativeTrait
|
||||
| Maimed => Some(isNoRequiredOrActiveSelection)
|
||||
| Incompetent =>
|
||||
// Checks if the advantage with the passed id is active
|
||||
let isAdvActive = id =>
|
||||
IM.lookup(Id.Advantage.toInt(id), hero.advantages) |> isActiveM;
|
||||
|
||||
// Checks if the passed select option does not represent a social skill
|
||||
let isNotSocialSkill = (so: SO.t) =>
|
||||
switch (so.id) {
|
||||
| Skill(id) =>
|
||||
IM.lookup(id, staticData.skills)
|
||||
|> O.any((skill: Skill.Static.t) =>
|
||||
skill.gr === Id.Skill.Group.toInt(Social)
|
||||
)
|
||||
|> (!)
|
||||
| _ => true
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ (
|
||||
x =>
|
||||
// Socially Adaptable and Inspire Confidence
|
||||
// require no Incompetence in social skills
|
||||
isAdvActive(SociallyAdaptable)
|
||||
|| isAdvActive(InspireConfidence)
|
||||
? isNotSocialSkill(x) : true
|
||||
),
|
||||
);
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
let id = Id.All.SpecialAbility(staticSpecialAbility.id);
|
||||
|
||||
let isNoRequiredOrActiveSelection =
|
||||
isNoGenericRequiredOrActiveSelectionShort(hero.specialAbilities, id);
|
||||
|
||||
let isNoRequiredSelection =
|
||||
isNoGenericRequiredSelectionShort(hero.specialAbilities, id);
|
||||
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (Id.SpecialAbility.fromInt(staticSpecialAbility.id)) {
|
||||
| SkillSpecialization =>
|
||||
let isFirstSpecializationValid = id =>
|
||||
IM.lookup(id, hero.skills)
|
||||
|> O.option(false, (skill: Skill.Dynamic.t)
|
||||
// Skill Rating must be at least 6 for 1st specialization
|
||||
=> skill.value >= 6);
|
||||
|
||||
let isAdditionalSpecializationValid =
|
||||
(counter, selectOption: SO.t, id) =>
|
||||
liftM2(
|
||||
(skill: Skill.Dynamic.t, activeApps) =>
|
||||
// Maximum of three is allowed
|
||||
L.lengthMax(2, activeApps)
|
||||
// Skill Rating must be at least 6/12/18 for 1st/2nd/3rd specialization
|
||||
&& skill.value >= (L.length(activeApps) + 1)
|
||||
* 6,
|
||||
IM.lookup(id, hero.skills),
|
||||
SOM.lookup(selectOption.id, counter),
|
||||
)
|
||||
|> O.dis;
|
||||
|
||||
let counter = O.Infix.(maybeHeroEntry <&> getActiveOptions2Map);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredSelection
|
||||
>>~ (
|
||||
switch (counter) {
|
||||
// if counter is available, maybeHeroEntry must be a Some and thus there
|
||||
// can be active selections
|
||||
| Some(counter) => (
|
||||
selectOption =>
|
||||
switch (selectOption.id) {
|
||||
| Skill(id) =>
|
||||
SOM.member(selectOption.id, counter)
|
||||
? isAdditionalSpecializationValid(
|
||||
counter,
|
||||
selectOption,
|
||||
id,
|
||||
)
|
||||
: isFirstSpecializationValid(id)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
// otherwise we only need to check if the skill rating is at least 6, as
|
||||
// there can't be an activated selection then.
|
||||
| None => (
|
||||
selectOption =>
|
||||
switch (selectOption.id) {
|
||||
| Skill(id) => isFirstSpecializationValid(id)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
<&~ (
|
||||
(selectOption: SO.t) => {
|
||||
let maybeCurrentCount =
|
||||
O.Infix.(counter >>= SOM.lookup(selectOption.id));
|
||||
|
||||
selectOption
|
||||
// Increase cost if there are active specializations
|
||||
// for the same skill
|
||||
|> O.liftDef(selectOption =>
|
||||
O.liftM2(
|
||||
(currentCount, selectOptionCost) =>
|
||||
{
|
||||
...selectOption,
|
||||
apValue:
|
||||
Some(
|
||||
selectOptionCost * (L.length(currentCount) + 1),
|
||||
),
|
||||
},
|
||||
maybeCurrentCount,
|
||||
selectOption.apValue,
|
||||
)
|
||||
)
|
||||
|> O.liftDef((selectOption: SO.t) =>
|
||||
Ley_Option.Infix.(
|
||||
(
|
||||
switch (selectOption.staticEntry) {
|
||||
| Some(Skill(skill)) => Some(skill)
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
<&> (
|
||||
skill =>
|
||||
skill.applications
|
||||
|> IM.elems
|
||||
|> L.filter(
|
||||
(application: Skill.Static.Application.t) => {
|
||||
// An application can only be selected once
|
||||
let isInactive =
|
||||
O.all(
|
||||
L.notElem(
|
||||
Id.Activatable.Option.Preset(
|
||||
Generic(application.id),
|
||||
),
|
||||
),
|
||||
maybeCurrentCount,
|
||||
);
|
||||
|
||||
// New applications must have valid prerequisites
|
||||
let hasValidPrerequisites =
|
||||
Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
id,
|
||||
switch (application.prerequisite) {
|
||||
| Some(prerequisite) => [
|
||||
{
|
||||
value: Activatable(prerequisite),
|
||||
displayOption: Generate,
|
||||
},
|
||||
]
|
||||
| None => []
|
||||
},
|
||||
);
|
||||
|
||||
isInactive && hasValidPrerequisites;
|
||||
})
|
||||
|> (
|
||||
applications => {
|
||||
...selectOption,
|
||||
applications: Some(applications),
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
| TraditionGuildMages =>
|
||||
// Filter all spells so that only spells are included that cant be
|
||||
// familiar spells
|
||||
Some(
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.disjoint(
|
||||
spell.traditions,
|
||||
Ley_IntSet.fromList([
|
||||
Id.MagicalTradition.toInt(General),
|
||||
Id.MagicalTradition.toInt(GuildMages),
|
||||
]),
|
||||
)
|
||||
),
|
||||
)
|
||||
| PropertyKnowledge as id
|
||||
| AspectKnowledge as id =>
|
||||
let availableIds =
|
||||
switch (id) {
|
||||
| PropertyKnowledge =>
|
||||
Spells.PropertyKnowledge.getAvailableProperties(
|
||||
staticData.spells,
|
||||
hero.spells,
|
||||
)
|
||||
| AspectKnowledge =>
|
||||
LiturgicalChants.AspectKnowledge.getAvailableAspects(
|
||||
staticData.liturgicalChants,
|
||||
hero.liturgicalChants,
|
||||
)
|
||||
// Never happens
|
||||
| _ => []
|
||||
};
|
||||
|
||||
let isValidId = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(id) => L.elem(id, availableIds)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(fold => fold |> isNoRequiredOrActiveSelection >>~ isValidId);
|
||||
| PropertyFocus =>
|
||||
let activePropertyKnowledges =
|
||||
maybeHeroEntry |> O.option([], getActiveSelectOptions1);
|
||||
|
||||
let hasActivePropertyKnowledge = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(_) as id => L.elem(id, activePropertyKnowledges)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ hasActivePropertyKnowledge,
|
||||
);
|
||||
| AdaptionZauber =>
|
||||
let minimumSkillRating = 10;
|
||||
|
||||
let hasSpellMinimumSr = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Spell(id) =>
|
||||
IM.lookup(id, hero.spells)
|
||||
|> O.option(false, (spell: ActivatableSkill.Dynamic.t) =>
|
||||
switch (spell.value) {
|
||||
| Active(value) => value >= minimumSkillRating
|
||||
| Inactive => false
|
||||
}
|
||||
)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
let isUnfamiliarSpell = (selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Spell(id) =>
|
||||
IM.lookup(id, staticData.spells)
|
||||
|> O.option(
|
||||
false,
|
||||
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
|
||||
hero.transferredUnfamiliarSpells,
|
||||
magicalTraditions,
|
||||
),
|
||||
)
|
||||
| _ => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ hasSpellMinimumSr
|
||||
>>~ isUnfamiliarSpell,
|
||||
);
|
||||
| SpellEnhancement as id
|
||||
| ChantEnhancement as id =>
|
||||
let getTargetHeroEntry =
|
||||
switch (id) {
|
||||
| SpellEnhancement => (id => IM.lookup(id, hero.spells))
|
||||
| ChantEnhancement => (id => IM.lookup(id, hero.liturgicalChants))
|
||||
| _ => (_ => None)
|
||||
};
|
||||
|
||||
let isNotUnfamiliar =
|
||||
Ley_Bool.notP(
|
||||
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
|
||||
hero.transferredUnfamiliarSpells,
|
||||
magicalTraditions,
|
||||
),
|
||||
);
|
||||
|
||||
let isValueValid = (maybeLevel, heroEntry: ActivatableSkill.Dynamic.t) =>
|
||||
switch (maybeLevel, heroEntry.value) {
|
||||
| (Some(level), Active(value)) => value >= level * 4 + 4
|
||||
| (None, Active(_))
|
||||
| (None, Inactive)
|
||||
| (Some(_), Inactive) => false
|
||||
};
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
>>~ (
|
||||
(selectOption: SO.t) =>
|
||||
switch (
|
||||
selectOption.staticEntry,
|
||||
O.Infix.(
|
||||
selectOption.enhancementTarget >>= getTargetHeroEntry
|
||||
),
|
||||
) {
|
||||
| (Some(Spell(staticSpell)), Some(heroSpell)) =>
|
||||
isNotUnfamiliar(staticSpell)
|
||||
&& isValueValid(selectOption.enhancementLevel, heroSpell)
|
||||
| (Some(LiturgicalChant(_)), Some(heroChant)) =>
|
||||
isValueValid(selectOption.enhancementLevel, heroChant)
|
||||
| _ => false
|
||||
}
|
||||
),
|
||||
);
|
||||
| LanguageSpecializations =>
|
||||
// Pair: fst = sid, snd = current_level
|
||||
let availableLanguages =
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(Language))
|
||||
|> O.option([], (heroLanguage: Activatable_Dynamic.t) =>
|
||||
heroLanguage.active
|
||||
|> O.mapOption((active: Activatable_Dynamic.single) =>
|
||||
O.Infix.(
|
||||
active.level
|
||||
>>= (
|
||||
level =>
|
||||
switch (level, active.options |> O.listToOption) {
|
||||
| (3 | 4, Some(Preset(Generic(sid)))) =>
|
||||
Some((sid, level))
|
||||
| _ => None
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
Some(
|
||||
fold =>
|
||||
fold
|
||||
|> isNoRequiredOrActiveSelection
|
||||
|> mapOptionT((selectOption: SO.t) =>
|
||||
switch (selectOption.id) {
|
||||
| Generic(sid) =>
|
||||
switch (L.lookup(sid, availableLanguages)) {
|
||||
| Some(4) => Some({...selectOption, apValue: Some(0)})
|
||||
| Some(3) => Some(selectOption)
|
||||
| Some(_)
|
||||
| None => None
|
||||
}
|
||||
| _ => None
|
||||
}
|
||||
),
|
||||
);
|
||||
| MadaschwesternStil =>
|
||||
Some(
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.disjoint(
|
||||
spell.traditions,
|
||||
Ley_IntSet.fromList([
|
||||
Id.MagicalTradition.toInt(General),
|
||||
Id.MagicalTradition.toInt(Witches),
|
||||
]),
|
||||
)
|
||||
),
|
||||
)
|
||||
| ScholarDesMagierkollegsZuHoningen =>
|
||||
let allowedTraditions =
|
||||
[Id.MagicalTradition.Druids, Elves, Witches]
|
||||
|> L.map(Id.MagicalTradition.toInt)
|
||||
|> IS.fromList;
|
||||
|
||||
let maybeTransferredSpellFromTradition =
|
||||
O.Infix.(
|
||||
hero.specialAbilities
|
||||
|> IM.lookup(Id.SpecialAbility.toInt(TraditionGuildMages))
|
||||
>>= (
|
||||
specialAbility =>
|
||||
specialAbility.active
|
||||
|> O.listToOption
|
||||
>>= (
|
||||
active =>
|
||||
(
|
||||
switch (active.options |> O.listToOption) {
|
||||
| Some(Preset(Spell(id))) => Some(id)
|
||||
| Some(_)
|
||||
| None => None
|
||||
}
|
||||
)
|
||||
>>= F.flip(IM.lookup, staticData.spells)
|
||||
<&> (spell => spell.traditions)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
maybeTransferredSpellFromTradition
|
||||
|> O.option(
|
||||
idT,
|
||||
transferredSpellFromTradition => {
|
||||
// Contains all allowed trads the first spell does not have
|
||||
let traditionDiff =
|
||||
IS.Infix.(allowedTraditions \/ transferredSpellFromTradition);
|
||||
|
||||
let hasTransferredSpellAllAllowedTraditions =
|
||||
IS.null(traditionDiff);
|
||||
|
||||
filterSpellSelectOptions(spell =>
|
||||
Ley_IntSet.notElem(
|
||||
Id.MagicalTradition.toInt(General),
|
||||
spell.traditions,
|
||||
)
|
||||
&& Ley_IntSet.any(
|
||||
F.flip(
|
||||
IS.elem,
|
||||
hasTransferredSpellAllAllowedTraditions
|
||||
? allowedTraditions : traditionDiff,
|
||||
),
|
||||
spell.traditions,
|
||||
)
|
||||
);
|
||||
},
|
||||
)
|
||||
|> O.return;
|
||||
| _ => Some(isNoRequiredOrActiveSelection)
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
let getAvailableSelectOptions =
|
||||
(
|
||||
staticData: Static.t,
|
||||
hero: Hero.t,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
) => {
|
||||
let allSelectOptions =
|
||||
Activatable_Accessors.selectOptions(staticEntry) |> SO.Map.elems;
|
||||
|
||||
getAvailableSelectOptionsTransducer(
|
||||
staticData,
|
||||
hero,
|
||||
magicalTraditions,
|
||||
staticEntry,
|
||||
maybeHeroEntry,
|
||||
)
|
||||
|> (
|
||||
fun
|
||||
| Some(transducer) =>
|
||||
switch (transduceList(transducer, allSelectOptions)) {
|
||||
| [] => L.null(allSelectOptions) ? Some([]) : None
|
||||
| xs => Some(xs)
|
||||
}
|
||||
| None => Some(allSelectOptions)
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* `getAvailableSelectOptions static hero magicalTraditions staticEntry
|
||||
* heroEntry` returns a list of possible select options for an inactive
|
||||
* activatable entry. Note: If `None` is returned, no select option is valid and
|
||||
* thus the entry cannot be added at all. This is different to a returned empty
|
||||
* list, which can happen if the entry just has no select options at all.
|
||||
*/
|
||||
let getAvailableSelectOptions:
|
||||
(
|
||||
Static.t,
|
||||
Hero.t,
|
||||
list(Tradition.Magical.fullTradition),
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
option(list(SelectOption.t));
|
||||
@@ -0,0 +1,245 @@
|
||||
module F = Ley_Function;
|
||||
module I = Ley_Int;
|
||||
module IM = Ley_IntMap;
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
open Activatable_Cache;
|
||||
|
||||
let getActivePactGiftsCount = specialAbilityPairs =>
|
||||
specialAbilityPairs
|
||||
|> EntryGroups.SpecialAbility.getFromGroup(Paktgeschenke)
|
||||
|> IM.foldr(
|
||||
fun
|
||||
| (
|
||||
{SpecialAbility.Static.prerequisites, apValue, levels, _},
|
||||
Some(({active, _}: Activatable_Dynamic.t) as heroEntry),
|
||||
)
|
||||
when Activatable_Accessors.isActive(heroEntry) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (prerequisites, apValue, levels, active) {
|
||||
| (
|
||||
// It must have different prerequisites for each level
|
||||
ByLevel(_),
|
||||
// It must have different cost per level
|
||||
Some(PerLevel(_)),
|
||||
// It must have levels
|
||||
Some(_),
|
||||
// The current level must be set
|
||||
[{level: Some(level), _}, ..._],
|
||||
) =>
|
||||
// Then each level counts as one activated pact present
|
||||
(+)(level)
|
||||
| _ => (+)(1)
|
||||
}
|
||||
)
|
||||
| _ => F.id,
|
||||
0,
|
||||
);
|
||||
|
||||
let isSpecialAbilitySpecificAdditionEnabled =
|
||||
(
|
||||
~rules: Hero.Rules.t,
|
||||
~maybePact,
|
||||
{
|
||||
combatStyleCombination,
|
||||
armedCombatStylesCount,
|
||||
unarmedCombatStylesCount,
|
||||
magicalStyleCombination,
|
||||
magicalStylesCount,
|
||||
isBlessedStyleActive,
|
||||
dunklesAbbild,
|
||||
activePactGiftsCount,
|
||||
matchingLanguagesScripts,
|
||||
_,
|
||||
},
|
||||
specialAbility: SpecialAbility.Static.t,
|
||||
) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.(
|
||||
switch (fromInt(specialAbility.id), Group.fromInt(specialAbility.gr)) {
|
||||
| (_, CombatStylesArmed as gr | CombatStylesUnarmed as gr) =>
|
||||
let equalTypeStylesActive =
|
||||
switch (gr) {
|
||||
| CombatStylesArmed => armedCombatStylesCount
|
||||
| CombatStylesUnarmed => unarmedCombatStylesCount
|
||||
| _ => 0
|
||||
};
|
||||
|
||||
// Combination-SA is active, which allows 3 styles to be active, but only a
|
||||
// maximum of 2 from one type (armed/unarmed).
|
||||
if (Activatable_Accessors.isActiveM(combatStyleCombination)) {
|
||||
let totalActive = armedCombatStylesCount + unarmedCombatStylesCount;
|
||||
|
||||
totalActive < 3 || equalTypeStylesActive < 2;
|
||||
} else {
|
||||
// Otherwise, only one of each type can be active.
|
||||
equalTypeStylesActive === 0;
|
||||
};
|
||||
|
||||
| (_, MagicalStyles) =>
|
||||
magicalStylesCount
|
||||
< (Activatable_Accessors.isActiveM(magicalStyleCombination) ? 2 : 1)
|
||||
|
||||
| (_, BlessedStyles) => !isBlessedStyleActive
|
||||
|
||||
| (CombatStyleCombination, _) =>
|
||||
armedCombatStylesCount + unarmedCombatStylesCount > 0
|
||||
|
||||
| (MagicStyleCombination, _) => magicalStylesCount > 0
|
||||
|
||||
| (DunklesAbbildDerBuendnisgabe, _) => activePactGiftsCount === 0
|
||||
|
||||
| (WegDerSchreiberin, _) =>
|
||||
L.length(matchingLanguagesScripts.languagesWithMatchingScripts) >= 1
|
||||
&& L.length(matchingLanguagesScripts.scriptsWithMatchingLanguages) >= 1
|
||||
|
||||
| (_, Paktgeschenke) =>
|
||||
switch (maybePact) {
|
||||
| Some((pact: Pact.Dynamic.t)) =>
|
||||
switch (Id.Pact.fromInt(pact.category)) {
|
||||
| Faery =>
|
||||
!Activatable_Accessors.isActiveM(dunklesAbbild)
|
||||
&& pact.level > activePactGiftsCount
|
||||
| Demon =>
|
||||
// Minor Pact
|
||||
pact.level <= 0
|
||||
// Minor Pact only allows up to 3 pact gifts
|
||||
? activePactGiftsCount < 3
|
||||
// "Normal" Pact allows up to CoD + 7 pact gifts
|
||||
: pact.level + 7 > activePactGiftsCount
|
||||
| Other(_) => false
|
||||
}
|
||||
| None => false
|
||||
}
|
||||
|
||||
| (LanguageSpecializations, _) =>
|
||||
IM.member(
|
||||
Id.OptionalRule.toInt(LanguageSpecialization),
|
||||
rules.activeOptionalRules,
|
||||
)
|
||||
|
||||
| (_, Vampirismus | Lykanthropie) =>
|
||||
// TODO: add option to activate vampire or lycanthropy and activate this
|
||||
// SAs based on that option
|
||||
false
|
||||
|
||||
| _ => true
|
||||
}
|
||||
);
|
||||
|
||||
let isEntrySpecificAdditionEnabled =
|
||||
(cache, staticData, hero: Hero.t, staticEntry) =>
|
||||
(
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
isSpecialAbilitySpecificAdditionEnabled(
|
||||
~rules=hero.rules,
|
||||
~maybePact=hero.pact,
|
||||
cache,
|
||||
staticSpecialAbility,
|
||||
)
|
||||
}
|
||||
)
|
||||
&& Prerequisites.Validation.arePrerequisitesMet(
|
||||
staticData,
|
||||
hero,
|
||||
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
|
||||
Prerequisites.Activatable.getFlatFirstPrerequisites(staticEntry),
|
||||
);
|
||||
|
||||
let hasNoGenerallyRestrictingDependency =
|
||||
fun
|
||||
| Some({Activatable_Dynamic.dependencies, _}) =>
|
||||
L.all(
|
||||
[@warning "-4"]
|
||||
(
|
||||
fun
|
||||
| {
|
||||
Activatable_Dynamic.target: One(_),
|
||||
options: [],
|
||||
level: None,
|
||||
active: false,
|
||||
_,
|
||||
} =>
|
||||
false
|
||||
| _ => true
|
||||
),
|
||||
dependencies,
|
||||
)
|
||||
| _ => true;
|
||||
|
||||
let hasNotReachedMaximumEntries = (maxLevel, maybeHeroEntry) =>
|
||||
switch (maxLevel, maybeHeroEntry) {
|
||||
// Impossible maximum level, thus no addition possible
|
||||
| (Some(0), _) => false
|
||||
| (Some(max), Some(({active, _}: Activatable_Dynamic.t))) =>
|
||||
max > L.length(active)
|
||||
| (Some(_), None)
|
||||
| (None, _) => true
|
||||
};
|
||||
|
||||
let isValidExtendedSpecialAbility =
|
||||
({validExtendedSpecialAbilities, _}, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(_)
|
||||
| Disadvantage(_) => true
|
||||
| SpecialAbility(staticSpecialAbility) =>
|
||||
[@warning "-4"]
|
||||
Id.SpecialAbility.Group.(
|
||||
switch (fromInt(staticSpecialAbility.gr)) {
|
||||
| CombatExtended
|
||||
| MagicalExtended
|
||||
| KarmaExtended
|
||||
| SkillExtended =>
|
||||
L.elem(staticSpecialAbility.id, validExtendedSpecialAbilities)
|
||||
| _ => true
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
let appliesToMagicalActionsIfRequired =
|
||||
({requiredApplyToMagicalActions, _}, staticEntry) =>
|
||||
switch (staticEntry) {
|
||||
| Activatable.Advantage(staticAdvantage) =>
|
||||
!requiredApplyToMagicalActions
|
||||
|| !staticAdvantage.isExclusiveToArcaneSpellworks
|
||||
| Disadvantage(staticDisadvantage) =>
|
||||
!requiredApplyToMagicalActions
|
||||
|| !staticDisadvantage.isExclusiveToArcaneSpellworks
|
||||
| SpecialAbility(_) => true
|
||||
};
|
||||
|
||||
let isAdditionValid =
|
||||
(cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry) =>
|
||||
isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry)
|
||||
&& hasNoGenerallyRestrictingDependency(maybeHeroEntry)
|
||||
&& hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry)
|
||||
&& isValidExtendedSpecialAbility(cache, staticEntry)
|
||||
&& appliesToMagicalActionsIfRequired(cache, staticEntry);
|
||||
|
||||
let getMaxLevel = (staticData, hero, staticEntry, maybeHeroEntry) =>
|
||||
staticEntry
|
||||
|> Prerequisites.Activatable.getLevelPrerequisites
|
||||
|> Dependencies.getMaxLevel(
|
||||
staticData,
|
||||
hero,
|
||||
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
|
||||
O.option(
|
||||
[],
|
||||
({Activatable_Dynamic.dependencies, _}) => dependencies,
|
||||
maybeHeroEntry,
|
||||
),
|
||||
)
|
||||
|> (
|
||||
computedMax =>
|
||||
switch (computedMax, Activatable_Accessors.max(staticEntry)) {
|
||||
| (Some(max1), Some(max2)) => Some(I.min(max1, max2))
|
||||
| (Some(max), None)
|
||||
| (None, Some(max)) => Some(max)
|
||||
| (None, None) => None
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Validate addition of inactive entries
|
||||
*
|
||||
* This module provides functions to determine if an (inactive) entry is allowed
|
||||
* to be bought/added.
|
||||
*/
|
||||
|
||||
/**
|
||||
* `getActivePactGiftsCount pairs` returns the amount of active pact gifts based
|
||||
* on the given set of static/hero special ability entry pairs.
|
||||
*/
|
||||
let getActivePactGiftsCount:
|
||||
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `isAdditionValid cache staticData hero maybeMaxLevel staticEntry
|
||||
* maybeHeroEntry` checks whether an activatable entry can be added/bought. The
|
||||
* `cache` contains computed values that are used for multiple entries so they
|
||||
* can be reused and thus improve performance for long lists of entries.
|
||||
*/
|
||||
let isAdditionValid:
|
||||
(
|
||||
Activatable_Cache.t,
|
||||
Static.t,
|
||||
Hero.t,
|
||||
option(int),
|
||||
Activatable.t,
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
bool;
|
||||
|
||||
/**
|
||||
* `getMaxLevel staticData staticEntry maybeHeroEntry` returns the maximum level based on the level prerequisites (excluding first level prerequisites) where the key is the level and the value the prerequisites that need to be met for the respective level as well as based on the registered `dependencies` of the entry.
|
||||
*
|
||||
* The return value is based on prerequisites *and* entry dependencies. To get
|
||||
* the max level only based on prerequisites, use
|
||||
* `Prerequisites.Validation.getMaxLevel`.
|
||||
*/
|
||||
let getMaxLevel:
|
||||
(Static.t, Hero.t, Activatable.t, option(Activatable_Dynamic.t)) =>
|
||||
option(int);
|
||||
@@ -0,0 +1,58 @@
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
|
||||
let getLevel = (maybeEntry: option(Activatable_Dynamic.t)) =>
|
||||
O.Infix.(
|
||||
maybeEntry
|
||||
>>= (entry => entry.active |> O.listToOption)
|
||||
>>= (active => active.level)
|
||||
|> O.sum
|
||||
);
|
||||
|
||||
/**
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value.
|
||||
*/
|
||||
let modifyByLevel = (value, inc, dec) =>
|
||||
value + getLevel(inc) - getLevel(dec);
|
||||
|
||||
/**
|
||||
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
|
||||
* function always returns `0`.
|
||||
*/
|
||||
let modifyByLevelM = (value, inc, dec) =>
|
||||
O.option(0, value => modifyByLevel(value, inc, dec), value);
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
|
||||
* the entry `inc`, that should increase the base, is active, it adds `1` to the
|
||||
* base. If the entry `dec`, that should decrease the base, is active, it
|
||||
* subtracts `1` from the base. If the passed base is `Nothing`, this function
|
||||
* always returns `0`.
|
||||
*/
|
||||
let getModifierByIsActive = (value, inc, dec) =>
|
||||
O.option(
|
||||
0,
|
||||
value =>
|
||||
Activatable_Accessors.isActiveM(inc)
|
||||
? value + 1 : Activatable_Accessors.isActiveM(dec) ? value - 1 : value,
|
||||
value,
|
||||
);
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
|
||||
* summing all active entries that should increase the base (`incs`) and all
|
||||
* active entries that should decrease the base (`decs`).
|
||||
*/
|
||||
let getModifierByIsActives = (value, incs, decs) =>
|
||||
O.option(
|
||||
0,
|
||||
value =>
|
||||
value
|
||||
+ L.countBy(Activatable_Accessors.isActiveM, incs)
|
||||
- L.countBy(Activatable_Accessors.isActiveM, decs),
|
||||
value,
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value.
|
||||
*/
|
||||
let modifyByLevel:
|
||||
(int, option(Activatable_Dynamic.t), option(Activatable_Dynamic.t)) => int;
|
||||
|
||||
/**
|
||||
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
|
||||
* the passed entries. The `inc` entry's level increases the value while the
|
||||
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
|
||||
* function always returns `0`.
|
||||
*/
|
||||
let modifyByLevelM:
|
||||
(
|
||||
option(int),
|
||||
option(Activatable_Dynamic.t),
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
|
||||
* the entry `inc`, that should increase the base, is active, it adds `1` to the
|
||||
* base. If the entry `dec`, that should decrease the base, is active, it
|
||||
* subtracts `1` from the base. If the passed base is `Nothing`, this function
|
||||
* always returns `0`.
|
||||
*/
|
||||
let getModifierByIsActive:
|
||||
(
|
||||
option(int),
|
||||
option(Activatable_Dynamic.t),
|
||||
option(Activatable_Dynamic.t)
|
||||
) =>
|
||||
int;
|
||||
|
||||
/**
|
||||
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
|
||||
* summing all active entries that should increase the base (`incs`) and all
|
||||
* active entries that should decrease the base (`decs`).
|
||||
*/
|
||||
let getModifierByIsActives:
|
||||
(
|
||||
option(int),
|
||||
list(option(Activatable_Dynamic.t)),
|
||||
list(option(Activatable_Dynamic.t))
|
||||
) =>
|
||||
int;
|
||||
@@ -0,0 +1,130 @@
|
||||
open SelectOption;
|
||||
open Ley_Option.Infix;
|
||||
open Activatable_Convert;
|
||||
open Activatable_Accessors;
|
||||
|
||||
module L = Ley_List;
|
||||
module O = Ley_Option;
|
||||
module SOM = SelectOption.Map;
|
||||
module F = Ley_Function;
|
||||
|
||||
let getSelectOption = (x, id) => Map.lookup(id, selectOptions(x));
|
||||
|
||||
/**
|
||||
* Get a selection option's name with the given id from given wiki entry.
|
||||
* Returns `None` if not found.
|
||||
*/
|
||||
let getSelectOptionName = (x, id) =>
|
||||
id |> getSelectOption(x) <&> (y => y.name);
|
||||
|
||||
/**
|
||||
* Get a selection option's cost with the given id from given wiki entry.
|
||||
* Returns `None` if not found.
|
||||
*/
|
||||
let getSelectOptionCost = (x, id) =>
|
||||
id |> getSelectOption(x) >>= (y => y.apValue);
|
||||
|
||||
/**
|
||||
* Get all option IDs from the given entry at the passed index.
|
||||
*/
|
||||
let getActiveOptions = (index, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
Ley_List.Safe.atMay(y.options, index)
|
||||
);
|
||||
|
||||
let mapActiveOptions = (f, index, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
Ley_List.Safe.atMay(y.options, index) >>= f
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all first option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions1 = (x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
y.options |> O.listToOption
|
||||
);
|
||||
|
||||
let mapActiveOptions1 = (f, x: Activatable_Dynamic.t) =>
|
||||
x.active
|
||||
|> O.mapOption((y: Activatable_Dynamic.single) =>
|
||||
y.options |> O.listToOption >>= f
|
||||
);
|
||||
|
||||
/**
|
||||
* Get all first select option IDs from the given entry.
|
||||
*/
|
||||
let getActiveSelectOptions1 =
|
||||
mapActiveOptions1(Activatable_Convert.activatableOptionToSelectOptionId);
|
||||
|
||||
/**
|
||||
* Get all second option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions2 = getActiveOptions(1);
|
||||
|
||||
/**
|
||||
* Get all second option ids from the given entry, sorted by their first option
|
||||
* id in a map.
|
||||
*/
|
||||
let getActiveOptions2Map = (x: Activatable_Dynamic.t) =>
|
||||
L.foldr(
|
||||
(current: Activatable_Dynamic.single, mp) =>
|
||||
current.options
|
||||
|> O.listToOption
|
||||
>>= Activatable_Convert.activatableOptionToSelectOptionId
|
||||
|> O.liftM2(
|
||||
(secondOption, option) =>
|
||||
SOM.alter(
|
||||
maybeSecondOptions =>
|
||||
maybeSecondOptions
|
||||
|> O.fromOption([])
|
||||
|> L.cons(secondOption)
|
||||
|> (xs => Some(xs)),
|
||||
option,
|
||||
mp,
|
||||
),
|
||||
current.options |> F.flip(L.Safe.atMay, 1),
|
||||
)
|
||||
|> O.fromOption(mp),
|
||||
SOM.empty,
|
||||
x.active,
|
||||
);
|
||||
|
||||
let getOption = (index, heroEntry) =>
|
||||
Ley_List.Safe.atMay(heroEntry.options, index);
|
||||
|
||||
let getOption1 = heroEntry => heroEntry.options |> Ley_Option.listToOption;
|
||||
|
||||
let getOption2 = getOption(1);
|
||||
|
||||
let getOption3 = getOption(2);
|
||||
|
||||
let getCustomInput = (option: Id.Activatable.Option.t) =>
|
||||
switch (option) {
|
||||
| CustomInput(x) => Some(x)
|
||||
| Preset(_) => None
|
||||
};
|
||||
|
||||
let getGenericId = (option: Id.Activatable.Option.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (option) {
|
||||
| Preset(Generic(x)) => Some(x)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
|
||||
let lookupMap = (k, mp, f) => f <$> Ley_IntMap.lookup(k, mp);
|
||||
|
||||
let getSkillFromOption =
|
||||
(staticData: Static.t, option: Id.Activatable.Option.t) =>
|
||||
[@warning "-4"]
|
||||
(
|
||||
switch (option) {
|
||||
| Preset(Skill(id)) => Ley_IntMap.lookup(id, staticData.skills)
|
||||
| _ => None
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Get specific Select Options.
|
||||
*
|
||||
* This module provides some utility functions for working with static and
|
||||
* active Select Options.
|
||||
*/
|
||||
|
||||
open Activatable_Convert;
|
||||
|
||||
/**
|
||||
* Get a select option with the given id from given static entry. Returns
|
||||
* `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOption:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(SelectOption.t);
|
||||
|
||||
/**
|
||||
* Get a select option's name with the given id from given static entry.
|
||||
* Returns `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOptionName:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(string);
|
||||
|
||||
/**
|
||||
* Get a option's cost with the given id from given static entry. Returns
|
||||
* `Nothing` if not found.
|
||||
*/
|
||||
let getSelectOptionCost:
|
||||
(Activatable.t, Id.Activatable.SelectOption.t) => option(int);
|
||||
|
||||
/**
|
||||
* Get all first option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions1: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
|
||||
|
||||
/**
|
||||
* `mapActiveOptions1 f entry` takes all first option ids from the given entry
|
||||
* and returns all values where the passed function, applied to each id,
|
||||
* returned a `Some`, which is then unwrapped.
|
||||
*/
|
||||
let mapActiveOptions1:
|
||||
(Id.Activatable.Option.t => option('a), Activatable_Dynamic.t) => list('a);
|
||||
|
||||
/**
|
||||
* Get all first select option IDs from the given entry.
|
||||
*/
|
||||
let getActiveSelectOptions1:
|
||||
Activatable_Dynamic.t => list(Id.Activatable.SelectOption.t);
|
||||
|
||||
/**
|
||||
* Get all second option IDs from the given entry.
|
||||
*/
|
||||
let getActiveOptions2: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
|
||||
|
||||
/**
|
||||
* Get all second option ids from the given entry, sorted by their first option
|
||||
* id in a map.
|
||||
*/
|
||||
let getActiveOptions2Map:
|
||||
Activatable_Dynamic.t => SelectOption.Map.t(list(Id.Activatable.Option.t));
|
||||
|
||||
let getOption: (int, singleWithId) => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption1: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption2: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getOption3: singleWithId => option(Id.Activatable.Option.t);
|
||||
|
||||
let getCustomInput: Id.Activatable.Option.t => option(string);
|
||||
|
||||
let getGenericId: Id.Activatable.Option.t => option(int);
|
||||
|
||||
let lookupMap: (Ley_IntMap.key, Ley_IntMap.t('b), 'b => 'a) => option('a);
|
||||
|
||||
let getSkillFromOption:
|
||||
(Static.t, Id.Activatable.Option.t) => option(Skill.Static.t);
|
||||
@@ -0,0 +1,464 @@
|
||||
module Id = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Name = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module NameInLibrary = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Levels = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Maximum = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module Input = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Rules = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Effect = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module CombatSpecialAbilityType = struct
|
||||
type t = Passive | BaseManeuver | SpecialManeuver
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
string
|
||||
|> map (function
|
||||
| "Passive" -> Passive
|
||||
| "BaseManeuver" -> BaseManeuver
|
||||
| "SpecialManeuver" -> SpecialManeuver
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError ("Unknown CombatSpecialAbilityType: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module Penalty = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module AeCost = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Volume = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module BindingCost = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Property = struct
|
||||
type t = DependingOnProperty | Single of int
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Passive" -> fun _ -> DependingOnProperty
|
||||
| "Single" -> field "value" int |> map (fun x -> Single x)
|
||||
| str -> raise (DecodeError ("Unknown Property: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module Aspect = struct
|
||||
type t = int
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.int
|
||||
end
|
||||
end
|
||||
|
||||
module AdvancedSpecialAbilities = struct
|
||||
type slot =
|
||||
| Single of {
|
||||
id : int;
|
||||
option :
|
||||
OptolithClient.Id.Activatable.SelectOption.t NonEmptyList.t option;
|
||||
(** Only one specific select option or one of a set of select
|
||||
options is allowed for the referenced advanced special
|
||||
ability. *)
|
||||
}
|
||||
| OneOf of int list
|
||||
|
||||
type t = slot * slot * slot
|
||||
|
||||
module Decode = struct
|
||||
let slot =
|
||||
Json_Decode_Strict.(
|
||||
oneOf
|
||||
[
|
||||
list int |> map (fun xs -> OneOf xs);
|
||||
(fun json ->
|
||||
Single
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
option =
|
||||
json
|
||||
|> optionalField "option"
|
||||
(NonEmptyList.Decode.one_or_many
|
||||
OptolithClient.Id.Activatable.SelectOption.Decode.t);
|
||||
});
|
||||
])
|
||||
|
||||
let t = Json.Decode.tuple3 slot slot slot
|
||||
end
|
||||
end
|
||||
|
||||
module ApplicableCombatTechniques = struct
|
||||
type generalRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of OptolithClient.Id.CombatTechnique.t list
|
||||
|
||||
type meleeRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| HasParry
|
||||
| OneHanded
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type rangedRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type specificRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| Level of int (** Only for a certain level of the special ability *)
|
||||
| Weapons of int list (** Only certain weapons *)
|
||||
|
||||
type specific = {
|
||||
id : OptolithClient.Id.CombatTechnique.t;
|
||||
restrictions : specificRestriction list;
|
||||
}
|
||||
|
||||
type t =
|
||||
| All of generalRestriction list
|
||||
| AllMelee of meleeRestriction list
|
||||
| AllRanged of rangedRestriction list
|
||||
| Specific of specific list
|
||||
| DependingOnCombatStyle
|
||||
| NotApplicable (** This is represented by a dash. *)
|
||||
|
||||
module Decode = struct
|
||||
let generalRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : generalRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : generalRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : generalRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : generalRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list OptolithClient.Id.CombatTechnique.Decode.t)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : generalRestriction))
|
||||
| str ->
|
||||
raise (DecodeError ("Unknown general restriction: " ^ str))))
|
||||
|
||||
let meleeRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : meleeRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : meleeRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : meleeRestriction)
|
||||
| "HasParry" -> fun _ -> HasParry
|
||||
| "OneHanded" -> fun _ -> OneHanded
|
||||
| "Race" ->
|
||||
field "value" int |> map (fun x -> (Race x : meleeRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : meleeRestriction))
|
||||
| str -> raise (DecodeError ("Unknown melee restriction: " ^ str))))
|
||||
|
||||
let rangedRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : rangedRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : rangedRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : rangedRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : rangedRestriction))
|
||||
| "ExcludeTechniques" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (ExcludeTechniques xs : rangedRestriction))
|
||||
| str -> raise (DecodeError ("Unknown ranged restriction: " ^ str))))
|
||||
|
||||
let specificRestriction =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Improvised" -> fun _ -> (Improvised : specificRestriction)
|
||||
| "PointedBlade" -> fun _ -> (PointedBlade : specificRestriction)
|
||||
| "Mount" -> fun _ -> (Mount : specificRestriction)
|
||||
| "Race" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Race x : specificRestriction))
|
||||
| "Level" ->
|
||||
field "value" int
|
||||
|> map (fun x -> (Level x : specificRestriction))
|
||||
| "Weapons" ->
|
||||
field "value" (list int)
|
||||
|> map (fun xs -> (Weapons xs : specificRestriction))
|
||||
| str ->
|
||||
raise (DecodeError ("Unknown specific restriction: " ^ str))))
|
||||
|
||||
let specific json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" OptolithClient.Id.CombatTechnique.Decode.t;
|
||||
restrictions =
|
||||
json
|
||||
|> optionalField "restrictions" (list specificRestriction)
|
||||
|> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "None" -> fun _ -> NotApplicable
|
||||
| "DependingOnCombatStyle" -> fun _ -> DependingOnCombatStyle
|
||||
| "All" ->
|
||||
field "value" (list generalRestriction)
|
||||
|> map (fun xs -> All xs)
|
||||
| "AllMelee" ->
|
||||
field "value" (list meleeRestriction)
|
||||
|> map (fun xs -> AllMelee xs)
|
||||
| "AllRanged" ->
|
||||
field "value" (list rangedRestriction)
|
||||
|> map (fun xs -> AllRanged xs)
|
||||
| "Specific" ->
|
||||
field "value" (list specific) |> map (fun xs -> Specific xs)
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError
|
||||
("Unknown applicable combat techniques option: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesReplacement = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesStart = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesEnd = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module ApValue = struct
|
||||
type t = Flat of int | PerLevel of int list | Option
|
||||
|
||||
module Decode = struct
|
||||
let t =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Flat" -> field "value" int |> map (fun x -> Flat x)
|
||||
| "PerLevel" ->
|
||||
field "value" (list int) |> map (fun xs -> PerLevel xs)
|
||||
| "Option" -> fun _ -> Option
|
||||
| str -> raise (DecodeError ("Unknown AP value: " ^ str))))
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueReplacement = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueAppend = struct
|
||||
type t = string
|
||||
|
||||
module Decode = struct
|
||||
let t = Json.Decode.string
|
||||
end
|
||||
end
|
||||
|
||||
module Decode = struct
|
||||
(** Types and functions that must be provided for an entity in order to derive
|
||||
the final decoders. *)
|
||||
module type Entity = sig
|
||||
type t
|
||||
|
||||
module Translation : Json_Decode_TranslationMap.EntityTranslation
|
||||
|
||||
type multilingual
|
||||
|
||||
val multilingual :
|
||||
(Js.Json.t -> Translation.t Json_Decode_TranslationMap.partial) ->
|
||||
Js.Json.t ->
|
||||
multilingual
|
||||
|
||||
val make :
|
||||
resolveSelectOptions:
|
||||
(src:PublicationRef.t list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
SelectOption.Decode.multilingual ->
|
||||
SelectOption.map) ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
Translation.t ->
|
||||
(int * t) option
|
||||
|
||||
(** Accessors are needed to provide the final translation to [make] as well as
|
||||
to get the id of the entry for [toAssoc]. *)
|
||||
module Accessors : sig
|
||||
val translations :
|
||||
multilingual -> Translation.t Json_Decode_TranslationMap.partial
|
||||
end
|
||||
end
|
||||
|
||||
type 'a decodeAssoc =
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:OptolithClient.Property.t Ley_IntMap.t ->
|
||||
aspects:OptolithClient.Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
Locale.Order.t ->
|
||||
Js.Json.t ->
|
||||
(int * 'a) option
|
||||
|
||||
(** Functor to derive the final decoders from the types and functions for the
|
||||
entity. *)
|
||||
module Make (E : Entity) = struct
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (E.Translation)
|
||||
|
||||
let multilingual = E.multilingual TranslationMap.t
|
||||
|
||||
let resolveTranslations ~resolveSelectOptions langs x =
|
||||
Ley_Option.Infix.(
|
||||
x |> E.Accessors.translations
|
||||
|> TranslationMap.getFromLanguageOrder langs
|
||||
>>= E.make ~resolveSelectOptions langs x)
|
||||
|
||||
let assoc ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals langs json =
|
||||
json |> multilingual
|
||||
|> resolveTranslations
|
||||
~resolveSelectOptions:
|
||||
(SelectOption.Decode.resolve ~blessings ~cantrips ~trade_secrets
|
||||
~languages ~scripts ~animal_shapes ~animal_shape_sizes
|
||||
~arcane_bard_traditions ~arcane_dancer_traditions ~elements
|
||||
~properties ~aspects ~diseases ~poisons ~melee_combat_techniques
|
||||
~ranged_combat_techniques ~liturgical_chants ~ceremonies ~skills
|
||||
~spells ~rituals)
|
||||
langs
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,335 @@
|
||||
module Id : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Name : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module NameInLibrary : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Levels : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Maximum : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Input : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Rules : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Effect : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module CombatSpecialAbilityType : sig
|
||||
type t = Passive | BaseManeuver | SpecialManeuver
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Penalty : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module AeCost : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Volume : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module BindingCost : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Property : sig
|
||||
type t = DependingOnProperty | Single of int
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Aspect : sig
|
||||
type t = int
|
||||
|
||||
module Decode : sig
|
||||
val t : int Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module AdvancedSpecialAbilities : sig
|
||||
type slot =
|
||||
| Single of {
|
||||
id : int;
|
||||
option :
|
||||
OptolithClient.Id.Activatable.SelectOption.t NonEmptyList.t option;
|
||||
(** Only one specific select option or one of a set of select
|
||||
options is allowed for the referenced advanced special
|
||||
ability. *)
|
||||
}
|
||||
| OneOf of int list
|
||||
|
||||
type t = slot * slot * slot
|
||||
|
||||
module Decode : sig
|
||||
val slot : slot Json.Decode.decoder
|
||||
|
||||
val t : (slot * slot * slot) Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApplicableCombatTechniques : sig
|
||||
type generalRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of OptolithClient.Id.CombatTechnique.t list
|
||||
|
||||
type meleeRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| HasParry
|
||||
| OneHanded
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type rangedRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| ExcludeTechniques of int list
|
||||
|
||||
type specificRestriction =
|
||||
| Improvised
|
||||
| PointedBlade
|
||||
| Mount
|
||||
| Race of int (** Only from a certain race *)
|
||||
| Level of int (** Only for a certain level of the special ability *)
|
||||
| Weapons of int list (** Only certain weapons *)
|
||||
|
||||
type specific = {
|
||||
id : OptolithClient.Id.CombatTechnique.t;
|
||||
restrictions : specificRestriction list;
|
||||
}
|
||||
|
||||
type t =
|
||||
| All of generalRestriction list
|
||||
| AllMelee of meleeRestriction list
|
||||
| AllRanged of rangedRestriction list
|
||||
| Specific of specific list
|
||||
| DependingOnCombatStyle
|
||||
| NotApplicable (** This is represented by a dash. *)
|
||||
|
||||
module Decode : sig
|
||||
val generalRestriction : generalRestriction Json.Decode.decoder
|
||||
|
||||
val meleeRestriction : meleeRestriction Json.Decode.decoder
|
||||
|
||||
val rangedRestriction : rangedRestriction Json.Decode.decoder
|
||||
|
||||
val specificRestriction : specificRestriction Json.Decode.decoder
|
||||
|
||||
val specific : specific Json.Decode.decoder
|
||||
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesReplacement : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesStart : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module PrerequisitesEnd : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValue : sig
|
||||
type t = Flat of int | PerLevel of int list | Option
|
||||
|
||||
module Decode : sig
|
||||
val t : t Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueReplacement : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module ApValueAppend : sig
|
||||
type t = string
|
||||
|
||||
module Decode : sig
|
||||
val t : string Json.Decode.decoder
|
||||
end
|
||||
end
|
||||
|
||||
module Decode : sig
|
||||
(** Required types, accessors and decoders to compose the final decoder *)
|
||||
module type Entity = sig
|
||||
type t
|
||||
(** The entity record type. *)
|
||||
|
||||
module Translation : Json_Decode_TranslationMap.EntityTranslation
|
||||
|
||||
type multilingual
|
||||
(** The record type with language-independent types. It represents the
|
||||
equivalent of a YAML file's content. Any adjustments to improve the
|
||||
usability in code should be handled in [make]. *)
|
||||
|
||||
val multilingual :
|
||||
(Js.Json.t -> Translation.t Json_Decode_TranslationMap.partial) ->
|
||||
Js.Json.t ->
|
||||
multilingual
|
||||
(** [t decodeTranslations json] decodes the passed JSON as the record with
|
||||
language-independent values. It has to decode the language-dependent values
|
||||
using [decodeTranslations].
|
||||
|
||||
@raise [DecodeError] if one decoder did not succeed. *)
|
||||
|
||||
val make :
|
||||
resolveSelectOptions:
|
||||
(src:PublicationRef.t list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
SelectOption.Decode.multilingual ->
|
||||
SelectOption.map) ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
Translation.t ->
|
||||
(int * t) option
|
||||
(** [make langs multilingual translation] merges the multilingual record with
|
||||
the record of the most wanted language available and should handle any
|
||||
type conversions that differ from the JSON schema for the YAML files. If
|
||||
inputs needs to be verified logically, this can be done here and [None]
|
||||
can be returned to exclude invalid entries. [langs] can be used to resolve
|
||||
translations of nested entities. *)
|
||||
|
||||
(** Accessors for record properties needed because of abstract types. *)
|
||||
module Accessors : sig
|
||||
val translations :
|
||||
multilingual -> Translation.t Json_Decode_TranslationMap.partial
|
||||
(** [translations x] returns the [translations] property from the
|
||||
[multilingual] record. *)
|
||||
end
|
||||
end
|
||||
|
||||
type 'a decodeAssoc =
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:OptolithClient.Property.t Ley_IntMap.t ->
|
||||
aspects:OptolithClient.Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
Locale.Order.t ->
|
||||
Js.Json.t ->
|
||||
(int * 'a) option
|
||||
|
||||
(** Create unified decoders using the passed entity module. *)
|
||||
module Make (E : Entity) : sig
|
||||
val assoc : E.t decodeAssoc
|
||||
(** [assoc localeOrder json] decodes the passed JSON into a value of type [t]
|
||||
of the passed module. It returns [None] if no matching translation could
|
||||
be found. It returns a pair to be used for inserting into a map.
|
||||
|
||||
@raise [DecodeError] if one decoder did not succeed. *)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
range = json |> optionalField "range" string;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
noMaxAPInfluence : bool option;
|
||||
isExclusiveToArcaneSpellworks : bool option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
gr : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
noMaxAPInfluence = json |> optionalField "noMaxAPInfluence" bool;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
json |> optionalField "isExclusiveToArcaneSpellworks" bool;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
gr = json |> field "gr" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
noMaxAPInfluence =
|
||||
multilingual.noMaxAPInfluence |> Ley_Option.fromOption false;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
multilingual.isExclusiveToArcaneSpellworks
|
||||
|> Ley_Option.fromOption false;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
range = translation.range;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
gr = multilingual.gr;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,31 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,64 @@
|
||||
type t = { id : int; name : string; path : int; size : int }
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = { name : string }
|
||||
|
||||
let t json = Json.Decode.{ name = json |> field "name" string }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
path : int;
|
||||
size : int;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
path = json |> field "path" int;
|
||||
size = json |> field "size" int;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make _ (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
path = multilingual.path;
|
||||
size = multilingual.size;
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
|
||||
module Size = struct
|
||||
type t = { id : int; volume : int; apValue : int }
|
||||
|
||||
module Decode = struct
|
||||
let t json =
|
||||
Some
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
volume = json |> field "volume" int;
|
||||
apValue = json |> field "apValue" int;
|
||||
}
|
||||
|
||||
let toAssoc (x : t) = (x.id, x)
|
||||
|
||||
let assoc (_ : Locale.Order.t) json =
|
||||
Ley_Option.Infix.(json |> t <&> toAssoc)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.t;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = { name : string }
|
||||
|
||||
let t json = Json_Decode_Strict.{ name = json |> field "name" string }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.Decode.multilingual;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json.Decode.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.ArcaneTradition.Decode.multilingual;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.ArcaneTradition.Decode.resolveTranslations
|
||||
langs multilingual.prerequisites;
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,9 @@
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
prerequisites : Prerequisite.Collection.ArcaneTradition.t;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
effect = json |> field "effect" string;
|
||||
range = json |> field "range" string;
|
||||
duration = json |> field "duration" string;
|
||||
target = json |> field "target" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
effect = translation.effect;
|
||||
range = translation.range;
|
||||
duration = translation.duration;
|
||||
target = translation.target;
|
||||
src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src;
|
||||
errata = translation.errata |> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
property : int;
|
||||
traditions : Ley_IntSet.t;
|
||||
prerequisites : Prerequisite.Collection.Activatable.t;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Json_Decode_Static.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
effect = json |> field "effect" string;
|
||||
range = json |> field "range" string;
|
||||
duration = json |> field "duration" string;
|
||||
target = json |> field "target" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
property : int;
|
||||
traditions : int list;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.Activatable.Decode.multilingual option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
property = json |> field "property" int;
|
||||
traditions = json |> field "traditions" (list int);
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.Activatable.Decode.multilingual;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make langs (multilingual : multilingual) (translation : Translation.t) =
|
||||
Some
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
effect = translation.effect;
|
||||
range = translation.range;
|
||||
duration = translation.duration;
|
||||
target = translation.target;
|
||||
property = multilingual.property;
|
||||
traditions = multilingual.traditions |> Ley_IntSet.fromList;
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
|> Ley_Option.option []
|
||||
(Prerequisite.Collection.Activatable.Decode.resolveTranslations
|
||||
langs);
|
||||
src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src;
|
||||
errata = translation.errata |> Ley_Option.fromOption [];
|
||||
}
|
||||
|
||||
module Accessors = struct
|
||||
let id (x : t) = x.id
|
||||
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
effect : string;
|
||||
range : string;
|
||||
duration : string;
|
||||
target : string;
|
||||
property : int;
|
||||
traditions : Ley_IntSet.t;
|
||||
prerequisites : Prerequisite.Collection.Activatable.t;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Json_Decode_Static.decodeAssoc
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,164 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
range = json |> optionalField "range" string;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
noMaxAPInfluence : bool option;
|
||||
isExclusiveToArcaneSpellworks : bool option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
gr : int;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
noMaxAPInfluence = json |> optionalField "noMaxAPInfluence" bool;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
json |> optionalField "isExclusiveToArcaneSpellworks" bool;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
gr = json |> field "gr" int;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
noMaxAPInfluence =
|
||||
multilingual.noMaxAPInfluence |> Ley_Option.fromOption false;
|
||||
isExclusiveToArcaneSpellworks =
|
||||
multilingual.isExclusiveToArcaneSpellworks
|
||||
|> Ley_Option.fromOption false;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
range = translation.range;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
gr = multilingual.gr;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,31 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
noMaxAPInfluence : bool;
|
||||
isExclusiveToArcaneSpellworks : bool;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
range : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
gr : int;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,970 @@
|
||||
type staticEntry =
|
||||
| Blessing of Blessing.Static.t
|
||||
| Cantrip of Cantrip.Static.t
|
||||
| TradeSecret of TradeSecret.t
|
||||
| Language of Language.t
|
||||
| Script of Script.t
|
||||
| AnimalShape of AnimalShape.t
|
||||
| SpellEnhancement of Enhancement.t
|
||||
| LiturgicalChantEnhancement of Enhancement.t
|
||||
| ArcaneBardTradition of ArcaneTradition.t
|
||||
| ArcaneDancerTradition of ArcaneTradition.t
|
||||
| Element of Element.t
|
||||
| Property of Property.t
|
||||
| Aspect of Aspect.t
|
||||
| Disease of Disease.t
|
||||
| Poison of Poison.t
|
||||
| MeleeCombatTechnique of CombatTechnique.Melee.Static.t
|
||||
| RangedCombatTechnique of CombatTechnique.Ranged.Static.t
|
||||
| LiturgicalChant of LiturgicalChant.Static.t
|
||||
| Ceremony of Ceremony.Static.t
|
||||
| Skill of Skill.Static.t
|
||||
| Spell of Spell.Static.t
|
||||
| Ritual of Ritual.Static.t
|
||||
|
||||
type t = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
name : string;
|
||||
apValue : int option;
|
||||
prerequisites : Prerequisite.Collection.General.t;
|
||||
description : string option;
|
||||
isSecret : bool option;
|
||||
languages : int list option;
|
||||
continent : int option;
|
||||
isExtinct : bool option;
|
||||
specializations : string list option;
|
||||
specializationInput : string option;
|
||||
animalGr : int option;
|
||||
animalLevel : int option;
|
||||
enhancementTarget : int option;
|
||||
enhancementLevel : int option;
|
||||
staticEntry : staticEntry option;
|
||||
applications : Skill.Static.Application.t list option;
|
||||
(** needed to be able to filter valid applications without altering the static entry *)
|
||||
src : PublicationRef.t list;
|
||||
errata : Erratum.t list;
|
||||
}
|
||||
|
||||
module Map = Ley_Map.Make (Id.Activatable.SelectOption)
|
||||
|
||||
type map = t Map.t
|
||||
|
||||
module Decode = struct
|
||||
module F = Ley_Function
|
||||
module L = Ley_List
|
||||
module O = Ley_Option
|
||||
module IM = Ley_IntMap
|
||||
|
||||
module Conversion = struct
|
||||
let make ~id ~name ?apValue ~staticEntry ~src ~errata () =
|
||||
{
|
||||
id;
|
||||
name;
|
||||
apValue;
|
||||
prerequisites = Plain [];
|
||||
description = None;
|
||||
isSecret = None;
|
||||
languages = None;
|
||||
continent = None;
|
||||
isExtinct = None;
|
||||
specializations = None;
|
||||
specializationInput = None;
|
||||
animalGr = None;
|
||||
animalLevel = None;
|
||||
enhancementTarget = None;
|
||||
enhancementLevel = None;
|
||||
staticEntry = Some staticEntry;
|
||||
applications = None;
|
||||
src;
|
||||
errata;
|
||||
}
|
||||
|
||||
let from_blessing (x : Blessing.Static.t) =
|
||||
make ~id:(Blessing x.id) ~name:x.name ~staticEntry:(Blessing x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_cantrip (x : Cantrip.Static.t) =
|
||||
make ~id:(Cantrip x.id) ~name:x.name ~staticEntry:(Cantrip x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_melee_combat_technique (x : CombatTechnique.Melee.Static.t) =
|
||||
make ~id:(MeleeCombatTechnique x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(MeleeCombatTechnique x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ranged_combat_technique (x : CombatTechnique.Ranged.Static.t) =
|
||||
make ~id:(RangedCombatTechnique x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(RangedCombatTechnique x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_liturgical_chant (x : LiturgicalChant.Static.t) =
|
||||
make ~id:(LiturgicalChant x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(LiturgicalChant x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ceremony (x : Ceremony.Static.t) =
|
||||
make ~id:(Ceremony x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Ceremony x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_skill (x : Skill.Static.t) =
|
||||
make ~id:(Skill x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Skill x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_spell (x : Spell.Static.t) =
|
||||
make ~id:(Spell x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Spell x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_ritual (x : Ritual.Static.t) =
|
||||
make ~id:(Ritual x.id) ~name:x.name
|
||||
~apValue:(IC.getApForActivatation x.ic)
|
||||
~staticEntry:(Ritual x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_trade_secret (x : TradeSecret.t) =
|
||||
make ~id:(TradeSecret x.id) ~name:x.name ~staticEntry:(TradeSecret x)
|
||||
~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_language (x : Language.t) =
|
||||
make ~id:(Language x.id) ~name:x.name ~staticEntry:(Language x) ~src:x.src
|
||||
~errata:x.errata ()
|
||||
|
||||
let from_script (x : Script.t) =
|
||||
make ~id:(Script x.id) ~name:x.name ~apValue:x.apValue
|
||||
~staticEntry:(Script x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_animal_shape animal_shape_sizes src errata (x : AnimalShape.t) =
|
||||
make ~id:(AnimalShape x.id) ~name:x.name
|
||||
?apValue:
|
||||
O.Infix.(
|
||||
animal_shape_sizes |> Ley_IntMap.lookup x.size
|
||||
<&> fun { AnimalShape.Size.apValue; _ } -> apValue)
|
||||
~staticEntry:(AnimalShape x) ~src ~errata ()
|
||||
|
||||
let from_arcane_bard_tradition src errata (x : ArcaneTradition.t) =
|
||||
make ~id:(ArcaneBardTradition x.id) ~name:x.name
|
||||
~staticEntry:(ArcaneBardTradition x) ~src ~errata ()
|
||||
|
||||
let from_arcane_dancer_tradition src errata (x : ArcaneTradition.t) =
|
||||
make ~id:(ArcaneDancerTradition x.id) ~name:x.name
|
||||
~staticEntry:(ArcaneDancerTradition x) ~src ~errata ()
|
||||
|
||||
let from_disease (x : Disease.t) =
|
||||
make ~id:(Disease x.id) ~name:x.name ~apValue:x.level
|
||||
~staticEntry:(Disease x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_poison (x : Poison.t) =
|
||||
make ~id:(Poison x.id) ~name:x.name
|
||||
~apValue:(match x.level with QL -> 6 | Fixed level -> level)
|
||||
~staticEntry:(Poison x) ~src:x.src ~errata:x.errata ()
|
||||
|
||||
let from_aspect src errata (x : Aspect.t) =
|
||||
make ~id:(Aspect x.id) ~name:x.name ~staticEntry:(Aspect x) ~src ~errata
|
||||
()
|
||||
|
||||
let from_element src errata (x : Element.t) =
|
||||
make ~id:(Element x.id) ~name:x.name ~staticEntry:(Element x) ~src ~errata
|
||||
()
|
||||
|
||||
let from_property src errata (x : Property.t) =
|
||||
make ~id:(Property x.id) ~name:x.name ~staticEntry:(Property x) ~src
|
||||
~errata ()
|
||||
end
|
||||
|
||||
module Derived = struct
|
||||
type select_option = t
|
||||
|
||||
type selectOptionPrerequisite = {
|
||||
target : Id.Activatable.t;
|
||||
active : bool;
|
||||
level : int option;
|
||||
}
|
||||
|
||||
let selectOptionPrerequisite json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
target = json |> field "target" Id.Activatable.Decode.t;
|
||||
active = json |> field "active" bool;
|
||||
level = json |> optionalField "level" int;
|
||||
}
|
||||
|
||||
type increasablePrerequisite =
|
||||
| Self of int
|
||||
| SelectOption of selectOptionPrerequisite
|
||||
|
||||
let increasablePrerequisite =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Self" -> field "value" int |> map (fun x -> Self x)
|
||||
| "SelectOption" ->
|
||||
field "value" selectOptionPrerequisite
|
||||
|> map (fun x -> SelectOption x)
|
||||
| str -> raise (DecodeError ("Unknown prerequisite type: " ^ str))))
|
||||
|
||||
type increasableApValue =
|
||||
| DerivedFromIC of int
|
||||
| Fixed of { list : int Ley_IntMap.t; default : int }
|
||||
|
||||
let increasableApValue =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "DerivedFromIC" ->
|
||||
field "value" int |> map (fun x -> DerivedFromIC x)
|
||||
| "Fixed" ->
|
||||
let element json =
|
||||
(json |> field "id" int, json |> field "apValue" int)
|
||||
in
|
||||
let fixed json =
|
||||
Fixed
|
||||
{
|
||||
list =
|
||||
json
|
||||
|> field "list" (list element)
|
||||
|> Ley_IntMap.fromList;
|
||||
default = json |> field "default" int;
|
||||
}
|
||||
in
|
||||
field "value" fixed
|
||||
| str -> raise (DecodeError ("Unknown AP value type: " ^ str))))
|
||||
|
||||
type specific = Include of int list | Exclude of int list
|
||||
|
||||
let specific json =
|
||||
Ley_Option.Infix.(
|
||||
Json_Decode_Strict.(
|
||||
let include_list =
|
||||
json |> optionalField "specific" (list int) <&> fun xs -> Include xs
|
||||
in
|
||||
let exclude_list =
|
||||
json |> optionalField "exclude" (list int) <&> fun xs -> Exclude xs
|
||||
in
|
||||
include_list <|> exclude_list))
|
||||
|
||||
type skillWithoutGroups = {
|
||||
specific : specific option;
|
||||
prerequisites : increasablePrerequisite list option;
|
||||
apValue : increasableApValue option;
|
||||
}
|
||||
|
||||
let skillWithoutGroups json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
specific = json |> specific;
|
||||
prerequisites =
|
||||
json |> optionalField "prerequisites" (list increasablePrerequisite);
|
||||
apValue = json |> optionalField "apValue" increasableApValue;
|
||||
}
|
||||
|
||||
type t =
|
||||
| Blessings
|
||||
| Cantrips
|
||||
| TradeSecrets
|
||||
| Scripts
|
||||
| AnimalShapes
|
||||
| ArcaneBardTraditions
|
||||
| ArcaneDancerTraditions
|
||||
| Elements of { specific : specific option }
|
||||
| Properties of { requireKnowledge : bool option }
|
||||
| Aspects of {
|
||||
requireKnowledge : bool option;
|
||||
useMasterOfSuffixAsName : bool option;
|
||||
}
|
||||
| Diseases of { useHalfLevelAsApValue : bool option }
|
||||
| Poisons of { useHalfLevelAsApValue : bool option }
|
||||
| Languages of { prerequisites : selectOptionPrerequisite list option }
|
||||
| MeleeCombatTechniques of skillWithoutGroups
|
||||
| RangedCombatTechniques of skillWithoutGroups
|
||||
| LiturgicalChants of skillWithoutGroups
|
||||
| Ceremonies of skillWithoutGroups
|
||||
| Skills of {
|
||||
groups : int list option;
|
||||
specific : specific option;
|
||||
prerequisites : increasablePrerequisite list option;
|
||||
apValue : increasableApValue option;
|
||||
}
|
||||
| Spells of skillWithoutGroups
|
||||
| Rituals of skillWithoutGroups
|
||||
|
||||
let t =
|
||||
Json_Decode_Strict.(
|
||||
field "category" string
|
||||
|> andThen (function
|
||||
| "Blessings" -> fun _ -> Blessings
|
||||
| "Cantrips" -> fun _ -> Cantrips
|
||||
| "TradeSecrets" -> fun _ -> TradeSecrets
|
||||
| "Scripts" -> fun _ -> Scripts
|
||||
| "AnimalShapes" -> fun _ -> AnimalShapes
|
||||
| "ArcaneBardTraditions" -> fun _ -> ArcaneBardTraditions
|
||||
| "ArcaneDancerTraditions" -> fun _ -> ArcaneDancerTraditions
|
||||
| "Elements" ->
|
||||
fun json -> Elements { specific = json |> specific }
|
||||
| "Properties" ->
|
||||
fun json ->
|
||||
Properties
|
||||
{
|
||||
requireKnowledge =
|
||||
json |> optionalField "requireKnowledge" bool;
|
||||
}
|
||||
| "Aspects" ->
|
||||
fun json ->
|
||||
Aspects
|
||||
{
|
||||
requireKnowledge =
|
||||
json |> optionalField "requireKnowledge" bool;
|
||||
useMasterOfSuffixAsName =
|
||||
json |> optionalField "useMasterOfSuffixAsName" bool;
|
||||
}
|
||||
| "Diseases" ->
|
||||
fun json ->
|
||||
Diseases
|
||||
{
|
||||
useHalfLevelAsApValue =
|
||||
json |> optionalField "useHalfLevelAsApValue" bool;
|
||||
}
|
||||
| "Poisons" ->
|
||||
fun json ->
|
||||
Poisons
|
||||
{
|
||||
useHalfLevelAsApValue =
|
||||
json |> optionalField "useHalfLevelAsApValue" bool;
|
||||
}
|
||||
| "Languages" ->
|
||||
fun json ->
|
||||
Languages
|
||||
{
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
(list selectOptionPrerequisite);
|
||||
}
|
||||
| "MeleeCombatTechniques" ->
|
||||
skillWithoutGroups |> map (fun x -> MeleeCombatTechniques x)
|
||||
| "RangedCombatTechniques" ->
|
||||
skillWithoutGroups |> map (fun x -> RangedCombatTechniques x)
|
||||
| "LiturgicalChants" ->
|
||||
skillWithoutGroups |> map (fun x -> LiturgicalChants x)
|
||||
| "Ceremonies" -> skillWithoutGroups |> map (fun x -> Ceremonies x)
|
||||
| "Skills" ->
|
||||
fun json ->
|
||||
Skills
|
||||
{
|
||||
groups = json |> optionalField "groups" (list int);
|
||||
specific = json |> specific;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
(list increasablePrerequisite);
|
||||
apValue =
|
||||
json |> optionalField "apValue" increasableApValue;
|
||||
}
|
||||
| "Spells" -> skillWithoutGroups |> map (fun x -> Spells x)
|
||||
| "Rituals" -> skillWithoutGroups |> map (fun x -> Rituals x)
|
||||
| str -> raise (DecodeError ("Unknown derived category: " ^ str))))
|
||||
|
||||
module Resolve = struct
|
||||
let insertIntoMap mp s = Map.insert s.id s mp
|
||||
|
||||
let mapInsert f staticmp mp =
|
||||
Ley_IntMap.foldr
|
||||
(fun static mp -> static |> f |> insertIntoMap mp)
|
||||
mp staticmp
|
||||
|
||||
let mapFilterMapInsert f filterMaps staticmp mp =
|
||||
Ley_IntMap.foldr
|
||||
(fun static mp ->
|
||||
let filterMap select_option =
|
||||
L.foldl'
|
||||
(fun g -> function Some acc -> g static acc | None -> None)
|
||||
filterMaps (Some select_option)
|
||||
in
|
||||
static |> f |> filterMap |> O.option mp (insertIntoMap mp))
|
||||
mp staticmp
|
||||
|
||||
let resolve_specific_ids specific id =
|
||||
match specific with
|
||||
| Some specific ->
|
||||
let filter id =
|
||||
match specific with
|
||||
| Include xs -> L.elem id xs
|
||||
| Exclude xs -> L.notElem id xs
|
||||
in
|
||||
fun static x -> if filter (id static) then Some x else None
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_require_knowledge require_knowledge special_ability_id id =
|
||||
let create_prerequisite entry_id =
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Activatable
|
||||
{
|
||||
id = special_ability_id;
|
||||
active = true;
|
||||
level = None;
|
||||
options = [ entry_id ];
|
||||
})
|
||||
in
|
||||
match require_knowledge with
|
||||
| Some true ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites = Plain [ create_prerequisite (id static) ];
|
||||
}
|
||||
| Some false | None -> F.const O.return
|
||||
|
||||
let resolve_half_ap_value use_half_ap_value =
|
||||
match use_half_ap_value with
|
||||
| Some true ->
|
||||
fun _ (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
(* AP value has been set to full level by conversion script *)
|
||||
O.Infix.(
|
||||
x.apValue >>= fun apValue ->
|
||||
Js.Int.toFloat apValue /. 2.0
|
||||
|> Js.Math.ceil_int |> O.return);
|
||||
}
|
||||
| Some false | None -> F.const O.return
|
||||
|
||||
let resolve_select_option_prerequisite { target; active; level } id =
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Activatable
|
||||
{ id = target; active; level; options = [ id ] })
|
||||
|
||||
let resolve_select_option_prerequisites prerequisites id =
|
||||
match prerequisites with
|
||||
| Some ps ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites =
|
||||
Plain
|
||||
( ps
|
||||
|> L.map
|
||||
(F.flip resolve_select_option_prerequisite
|
||||
(id static)) );
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_increasable_prerequisites prerequisites increasable_id
|
||||
select_option_id =
|
||||
let resolve_increasable_prerequisite static = function
|
||||
| Self sr ->
|
||||
Prerequisite.make
|
||||
(Prerequisite.General.Increasable
|
||||
{ id = increasable_id static; value = sr })
|
||||
| SelectOption options ->
|
||||
resolve_select_option_prerequisite options
|
||||
(select_option_id static)
|
||||
in
|
||||
match prerequisites with
|
||||
| Some ps ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
prerequisites =
|
||||
Plain (ps |> L.map (resolve_increasable_prerequisite static));
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve_ap_value id ic ap_value =
|
||||
match ap_value with
|
||||
| Some (DerivedFromIC modifier) ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
Some (ic static |> IC.getApForActivatation |> ( * ) modifier);
|
||||
}
|
||||
| Some (Fixed { list; default }) ->
|
||||
fun static (x : select_option) ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
apValue =
|
||||
Some
|
||||
( id static |> F.flip IM.lookup list
|
||||
|> O.fromOption default );
|
||||
}
|
||||
| None -> F.const O.return
|
||||
|
||||
let resolve ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata
|
||||
xs =
|
||||
Ley_List.foldl'
|
||||
(function
|
||||
| Blessings -> mapInsert Conversion.from_blessing blessings
|
||||
| Cantrips -> mapInsert Conversion.from_cantrip cantrips
|
||||
| TradeSecrets ->
|
||||
mapInsert Conversion.from_trade_secret trade_secrets
|
||||
| Scripts -> mapInsert Conversion.from_script scripts
|
||||
| AnimalShapes ->
|
||||
mapInsert
|
||||
(Conversion.from_animal_shape animal_shape_sizes src errata)
|
||||
animal_shapes
|
||||
| ArcaneBardTraditions ->
|
||||
mapInsert
|
||||
(Conversion.from_arcane_bard_tradition src errata)
|
||||
arcane_bard_traditions
|
||||
| ArcaneDancerTraditions ->
|
||||
mapInsert
|
||||
(Conversion.from_arcane_dancer_tradition src errata)
|
||||
arcane_dancer_traditions
|
||||
| Elements { specific } ->
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_element src errata)
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Element.t) -> x.id);
|
||||
]
|
||||
elements
|
||||
| Properties { requireKnowledge } ->
|
||||
let property_knowledge_id =
|
||||
Id.Activatable.MagicalSpecialAbility
|
||||
(Id.SpecialAbility.MagicalSpecialAbility.toInt
|
||||
PropertyKnowledge)
|
||||
in
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_property src errata)
|
||||
[
|
||||
resolve_require_knowledge requireKnowledge
|
||||
property_knowledge_id (fun (x : Property.t) ->
|
||||
Property x.id);
|
||||
]
|
||||
properties
|
||||
| Aspects { requireKnowledge; useMasterOfSuffixAsName } ->
|
||||
let aspect_knowledge_id =
|
||||
Id.Activatable.KarmaSpecialAbility
|
||||
(Id.SpecialAbility.KarmaSpecialAbility.toInt
|
||||
AspectKnowledge)
|
||||
in
|
||||
mapFilterMapInsert
|
||||
(Conversion.from_aspect src errata)
|
||||
[
|
||||
resolve_require_knowledge requireKnowledge
|
||||
aspect_knowledge_id (fun (x : Aspect.t) -> Aspect x.id);
|
||||
( match useMasterOfSuffixAsName with
|
||||
| Some true ->
|
||||
fun { masterOfAspectSuffix; _ } x ->
|
||||
Some
|
||||
{
|
||||
x with
|
||||
name = masterOfAspectSuffix |> O.fromOption x.name;
|
||||
}
|
||||
| Some false | None -> F.const O.return );
|
||||
]
|
||||
aspects
|
||||
| Diseases { useHalfLevelAsApValue } ->
|
||||
mapFilterMapInsert Conversion.from_disease
|
||||
[ resolve_half_ap_value useHalfLevelAsApValue ]
|
||||
diseases
|
||||
| Poisons { useHalfLevelAsApValue } ->
|
||||
mapFilterMapInsert Conversion.from_poison
|
||||
[ resolve_half_ap_value useHalfLevelAsApValue ]
|
||||
poisons
|
||||
| Languages { prerequisites } ->
|
||||
mapFilterMapInsert Conversion.from_language
|
||||
[
|
||||
resolve_select_option_prerequisites prerequisites
|
||||
(fun (x : Language.t) -> Language x.id);
|
||||
]
|
||||
languages
|
||||
| MeleeCombatTechniques { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_melee_combat_technique
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : CombatTechnique.Melee.Static.t) ->
|
||||
MeleeCombatTechnique x.id)
|
||||
(fun (x : CombatTechnique.Melee.Static.t) ->
|
||||
MeleeCombatTechnique x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.id)
|
||||
(fun (x : CombatTechnique.Melee.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
melee_combat_techniques
|
||||
| RangedCombatTechniques { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ranged_combat_technique
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) ->
|
||||
RangedCombatTechnique x.id)
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) ->
|
||||
RangedCombatTechnique x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.id)
|
||||
(fun (x : CombatTechnique.Ranged.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
ranged_combat_techniques
|
||||
| LiturgicalChants { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_liturgical_chant
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : LiturgicalChant.Static.t) ->
|
||||
LiturgicalChant x.id)
|
||||
(fun (x : LiturgicalChant.Static.t) ->
|
||||
LiturgicalChant x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.id)
|
||||
(fun (x : LiturgicalChant.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
liturgical_chants
|
||||
| Ceremonies { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ceremony
|
||||
[
|
||||
resolve_specific_ids specific
|
||||
(fun (x : Ceremony.Static.t) -> x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Ceremony.Static.t) -> Ceremony x.id)
|
||||
(fun (x : Ceremony.Static.t) -> Ceremony x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Ceremony.Static.t) -> x.id)
|
||||
(fun (x : Ceremony.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
ceremonies
|
||||
| Skills { groups; specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_skill
|
||||
[
|
||||
( match groups with
|
||||
| Some included_groups ->
|
||||
fun static x ->
|
||||
if L.elem static.gr included_groups then Some x
|
||||
else None
|
||||
| None -> F.const O.return );
|
||||
resolve_specific_ids specific (fun (x : Skill.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Skill.Static.t) -> Skill x.id)
|
||||
(fun (x : Skill.Static.t) -> Skill x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Skill.Static.t) -> x.id)
|
||||
(fun (x : Skill.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
skills
|
||||
| Spells { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_spell
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Spell.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Spell.Static.t) -> Spell x.id)
|
||||
(fun (x : Spell.Static.t) -> Spell x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Spell.Static.t) -> x.id)
|
||||
(fun (x : Spell.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
spells
|
||||
| Rituals { specific; prerequisites; apValue } ->
|
||||
mapFilterMapInsert Conversion.from_ritual
|
||||
[
|
||||
resolve_specific_ids specific (fun (x : Ritual.Static.t) ->
|
||||
x.id);
|
||||
resolve_increasable_prerequisites prerequisites
|
||||
(fun (x : Ritual.Static.t) -> Ritual x.id)
|
||||
(fun (x : Ritual.Static.t) -> Ritual x.id);
|
||||
resolve_ap_value
|
||||
(fun (x : Ritual.Static.t) -> x.id)
|
||||
(fun (x : Ritual.Static.t) -> x.ic)
|
||||
apValue;
|
||||
]
|
||||
rituals)
|
||||
xs Map.empty
|
||||
end
|
||||
end
|
||||
|
||||
module Explicit = struct
|
||||
module Preset = struct
|
||||
module Translation = struct
|
||||
type t = { errata : Erratum.list option }
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{ errata = json |> optionalField "errata" Erratum.Decode.list }
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (Translation)
|
||||
|
||||
type multilingual = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.General.Decode.multilingual option;
|
||||
apValue : int option;
|
||||
src : PublicationRef.Decode.multilingual list option;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial option;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" Id.Activatable.SelectOption.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.General.Decode.multilingual;
|
||||
apValue = json |> optionalField "apValue" int;
|
||||
src =
|
||||
json |> optionalField "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> optionalField "translations" TranslationMap.t;
|
||||
}
|
||||
|
||||
let make get_preset langs (multilingual : multilingual)
|
||||
(maybe_translation : Translation.t option) =
|
||||
O.Infix.(
|
||||
get_preset multilingual.id <&> fun (preset : t) ->
|
||||
{
|
||||
preset with
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
<&> Prerequisite.Collection.General.Decode.resolveTranslations
|
||||
langs
|
||||
|> O.fromOption (Prerequisite.Collection.ByLevel.Plain []);
|
||||
apValue = multilingual.apValue <|> preset.apValue;
|
||||
src =
|
||||
multilingual.src
|
||||
|> O.option preset.src
|
||||
(PublicationRef.Decode.resolveTranslationsList langs);
|
||||
errata =
|
||||
maybe_translation
|
||||
>>= (fun translation -> translation.errata)
|
||||
|> O.fromOption preset.errata;
|
||||
})
|
||||
|
||||
let resolveTranslations get_preset langs (x : multilingual) =
|
||||
Ley_Option.Infix.(
|
||||
x.translations
|
||||
>>= TranslationMap.getFromLanguageOrder langs
|
||||
|> make get_preset langs x)
|
||||
|
||||
let resolveId ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata =
|
||||
O.Infix.(
|
||||
function
|
||||
| Id.Activatable.SelectOption.Generic _ -> None
|
||||
| Blessing id ->
|
||||
blessings |> Ley_IntMap.lookup id <&> Conversion.from_blessing
|
||||
| Cantrip id ->
|
||||
cantrips |> Ley_IntMap.lookup id <&> Conversion.from_cantrip
|
||||
| TradeSecret id ->
|
||||
trade_secrets |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_trade_secret
|
||||
| Language id ->
|
||||
languages |> Ley_IntMap.lookup id <&> Conversion.from_language
|
||||
| Script id ->
|
||||
scripts |> Ley_IntMap.lookup id <&> Conversion.from_script
|
||||
| AnimalShape id ->
|
||||
animal_shapes |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_animal_shape animal_shape_sizes src errata
|
||||
| ArcaneBardTradition id ->
|
||||
arcane_bard_traditions |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_arcane_bard_tradition src errata
|
||||
| ArcaneDancerTradition id ->
|
||||
arcane_dancer_traditions |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_arcane_dancer_tradition src errata
|
||||
| Element id ->
|
||||
elements |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_element src errata
|
||||
| Property id ->
|
||||
properties |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_property src errata
|
||||
| Aspect id ->
|
||||
aspects |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_aspect src errata
|
||||
| Disease id ->
|
||||
diseases |> Ley_IntMap.lookup id <&> Conversion.from_disease
|
||||
| Poison id ->
|
||||
poisons |> Ley_IntMap.lookup id <&> Conversion.from_poison
|
||||
| MeleeCombatTechnique id ->
|
||||
melee_combat_techniques |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_melee_combat_technique
|
||||
| RangedCombatTechnique id ->
|
||||
ranged_combat_techniques |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_ranged_combat_technique
|
||||
| LiturgicalChant id ->
|
||||
liturgical_chants |> Ley_IntMap.lookup id
|
||||
<&> Conversion.from_liturgical_chant
|
||||
| Ceremony id ->
|
||||
ceremonies |> Ley_IntMap.lookup id <&> Conversion.from_ceremony
|
||||
| Skill id -> skills |> Ley_IntMap.lookup id <&> Conversion.from_skill
|
||||
| Spell id -> spells |> Ley_IntMap.lookup id <&> Conversion.from_spell
|
||||
| Ritual id ->
|
||||
rituals |> Ley_IntMap.lookup id <&> Conversion.from_ritual)
|
||||
end
|
||||
|
||||
module Custom = struct
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
description : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" string;
|
||||
description = json |> optionalField "description" string;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
module TranslationMap = Json_Decode_TranslationMap.Make (Translation)
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.General.Decode.multilingual option;
|
||||
apValue : int option;
|
||||
src : PublicationRef.Decode.multilingual list option;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Prerequisite.Collection.General.Decode.multilingual;
|
||||
apValue = json |> optionalField "apValue" int;
|
||||
src =
|
||||
json |> optionalField "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" TranslationMap.t;
|
||||
}
|
||||
|
||||
let make main_src langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
O.Infix.
|
||||
{
|
||||
id = Generic multilingual.id;
|
||||
name = translation.name;
|
||||
description = translation.description;
|
||||
prerequisites =
|
||||
multilingual.prerequisites
|
||||
<&> Prerequisite.Collection.General.Decode.resolveTranslations
|
||||
langs
|
||||
|> O.fromOption (Prerequisite.Collection.ByLevel.Plain []);
|
||||
apValue = multilingual.apValue;
|
||||
isSecret = None;
|
||||
languages = None;
|
||||
continent = None;
|
||||
isExtinct = None;
|
||||
specializations = None;
|
||||
specializationInput = None;
|
||||
animalGr = None;
|
||||
animalLevel = None;
|
||||
enhancementTarget = None;
|
||||
enhancementLevel = None;
|
||||
applications = None;
|
||||
staticEntry = None;
|
||||
src =
|
||||
multilingual.src
|
||||
|> O.option main_src
|
||||
(PublicationRef.Decode.resolveTranslationsList langs);
|
||||
errata = translation.errata |> O.fromOption [];
|
||||
}
|
||||
|
||||
let resolveTranslations main_src langs x =
|
||||
Ley_Option.Infix.(
|
||||
x.translations
|
||||
|> TranslationMap.getFromLanguageOrder langs
|
||||
<&> make main_src langs x)
|
||||
end
|
||||
|
||||
type multilingual =
|
||||
| Preset of Preset.multilingual
|
||||
| Custom of Custom.multilingual
|
||||
|
||||
let multilingual =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Preset" ->
|
||||
field "value" Preset.multilingual |> map (fun x -> Preset x)
|
||||
| "Custom" ->
|
||||
field "value" Custom.multilingual |> map (fun x -> Custom x)
|
||||
| str ->
|
||||
raise
|
||||
(DecodeError ("Unknown explicit select option type: " ^ str))))
|
||||
|
||||
let resolveTranslations get_preset langs main_src = function
|
||||
| Preset multilingual ->
|
||||
multilingual |> Preset.resolveTranslations get_preset langs
|
||||
| Custom multilingual ->
|
||||
multilingual |> Custom.resolveTranslations main_src langs
|
||||
|
||||
let resolve get_preset langs main_src xs =
|
||||
let fold x =
|
||||
x
|
||||
|> resolveTranslations get_preset langs main_src
|
||||
|> O.option F.id (fun value -> Map.insert value.id value)
|
||||
in
|
||||
Ley_List.foldl' fold xs Map.empty
|
||||
end
|
||||
|
||||
type multilingual =
|
||||
| Derived of Derived.t list
|
||||
| Explicit of Explicit.multilingual list
|
||||
|
||||
let multilingual =
|
||||
Json.Decode.(
|
||||
field "type" string
|
||||
|> andThen (function
|
||||
| "Derived" ->
|
||||
field "value" (list Derived.t) |> map (fun x -> Derived x)
|
||||
| "Explicit" ->
|
||||
field "value" (list Explicit.multilingual)
|
||||
|> map (fun x -> Explicit x)
|
||||
| str -> raise (DecodeError ("Unknown select option type: " ^ str))))
|
||||
|
||||
let resolve ~blessings ~cantrips ~trade_secrets ~languages ~scripts
|
||||
~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata langs
|
||||
= function
|
||||
| Derived derived ->
|
||||
Derived.Resolve.resolve ~blessings ~cantrips ~trade_secrets ~languages
|
||||
~scripts ~animal_shapes ~animal_shape_sizes ~arcane_bard_traditions
|
||||
~arcane_dancer_traditions ~elements ~properties ~aspects ~diseases
|
||||
~poisons ~melee_combat_techniques ~ranged_combat_techniques
|
||||
~liturgical_chants ~ceremonies ~skills ~spells ~rituals ~src ~errata
|
||||
derived
|
||||
| Explicit explicit ->
|
||||
Explicit.resolve
|
||||
(Explicit.Preset.resolveId ~blessings ~cantrips ~trade_secrets
|
||||
~languages ~scripts ~animal_shapes ~animal_shape_sizes
|
||||
~arcane_bard_traditions ~arcane_dancer_traditions ~elements
|
||||
~properties ~aspects ~diseases ~poisons ~melee_combat_techniques
|
||||
~ranged_combat_techniques ~liturgical_chants ~ceremonies ~skills
|
||||
~spells ~rituals ~src ~errata)
|
||||
langs src explicit
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
type staticEntry =
|
||||
| Blessing of Blessing.Static.t
|
||||
| Cantrip of Cantrip.Static.t
|
||||
| TradeSecret of TradeSecret.t
|
||||
| Language of Language.t
|
||||
| Script of Script.t
|
||||
| AnimalShape of AnimalShape.t
|
||||
| SpellEnhancement of Enhancement.t
|
||||
| LiturgicalChantEnhancement of Enhancement.t
|
||||
| ArcaneBardTradition of ArcaneTradition.t
|
||||
| ArcaneDancerTradition of ArcaneTradition.t
|
||||
| Element of Element.t
|
||||
| Property of Property.t
|
||||
| Aspect of Aspect.t
|
||||
| Disease of Disease.t
|
||||
| Poison of Poison.t
|
||||
| MeleeCombatTechnique of CombatTechnique.Melee.Static.t
|
||||
| RangedCombatTechnique of CombatTechnique.Ranged.Static.t
|
||||
| LiturgicalChant of LiturgicalChant.Static.t
|
||||
| Ceremony of Ceremony.Static.t
|
||||
| Skill of Skill.Static.t
|
||||
| Spell of Spell.Static.t
|
||||
| Ritual of Ritual.Static.t
|
||||
|
||||
type t = {
|
||||
id : Id.Activatable.SelectOption.t;
|
||||
name : string;
|
||||
apValue : int option;
|
||||
prerequisites : Prerequisite.Collection.General.t;
|
||||
description : string option;
|
||||
isSecret : bool option;
|
||||
languages : int list option;
|
||||
continent : int option;
|
||||
isExtinct : bool option;
|
||||
specializations : string list option;
|
||||
specializationInput : string option;
|
||||
animalGr : int option;
|
||||
animalLevel : int option;
|
||||
enhancementTarget : int option;
|
||||
enhancementLevel : int option;
|
||||
staticEntry : staticEntry option;
|
||||
applications : Skill.Static.Application.t list option;
|
||||
(** needed to be able to filter valid applications without altering the
|
||||
static entry *)
|
||||
src : PublicationRef.t list;
|
||||
errata : Erratum.t list;
|
||||
}
|
||||
|
||||
module Map : Ley_Map.T with type key = Id.Activatable.SelectOption.t
|
||||
|
||||
type map = t Map.t
|
||||
|
||||
module Decode : sig
|
||||
type multilingual
|
||||
|
||||
val multilingual : multilingual Json.Decode.decoder
|
||||
|
||||
val resolve :
|
||||
blessings:Blessing.Static.t Ley_IntMap.t ->
|
||||
cantrips:Cantrip.Static.t Ley_IntMap.t ->
|
||||
trade_secrets:TradeSecret.t Ley_IntMap.t ->
|
||||
languages:Language.t Ley_IntMap.t ->
|
||||
scripts:Script.t Ley_IntMap.t ->
|
||||
animal_shapes:AnimalShape.t Ley_IntMap.t ->
|
||||
animal_shape_sizes:AnimalShape.Size.t Ley_IntMap.t ->
|
||||
arcane_bard_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
arcane_dancer_traditions:ArcaneTradition.t Ley_IntMap.t ->
|
||||
elements:Element.t Ley_IntMap.t ->
|
||||
properties:Property.t Ley_IntMap.t ->
|
||||
aspects:Aspect.t Ley_IntMap.t ->
|
||||
diseases:Disease.t Ley_IntMap.t ->
|
||||
poisons:Poison.t Ley_IntMap.t ->
|
||||
melee_combat_techniques:CombatTechnique.Melee.Static.t Ley_IntMap.t ->
|
||||
ranged_combat_techniques:CombatTechnique.Ranged.Static.t Ley_IntMap.t ->
|
||||
liturgical_chants:LiturgicalChant.Static.t Ley_IntMap.t ->
|
||||
ceremonies:Ceremony.Static.t Ley_IntMap.t ->
|
||||
skills:Skill.Static.t Ley_IntMap.t ->
|
||||
spells:Spell.Static.t Ley_IntMap.t ->
|
||||
rituals:Ritual.Static.t Ley_IntMap.t ->
|
||||
src:PublicationRef.list ->
|
||||
errata:Erratum.list ->
|
||||
Locale.Order.t ->
|
||||
multilingual ->
|
||||
map
|
||||
end
|
||||
@@ -0,0 +1,160 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Shared.CombatSpecialAbilityType.t;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
combatTechniques : Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
penalty = json |> optionalField "penalty" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Shared.CombatSpecialAbilityType.t;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
combatTechniques : Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
category =
|
||||
json |> field "max" Shared.CombatSpecialAbilityType.Decode.t;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
combatTechniques =
|
||||
json
|
||||
|> field "combatTechniques"
|
||||
Shared.ApplicableCombatTechniques.Decode.t;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
category = multilingual.category;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
penalty = translation.penalty;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
combatTechniques = multilingual.combatTechniques;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,30 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
category : Activatable_Shared.CombatSpecialAbilityType.t;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
penalty : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
combatTechniques : Activatable_Shared.ApplicableCombatTechniques.t;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,144 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,27 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -0,0 +1,144 @@
|
||||
module Shared = Activatable_Shared
|
||||
|
||||
module Static = struct
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode = Shared.Decode.Make (struct
|
||||
type nonrec t = t
|
||||
|
||||
module Translation = struct
|
||||
type t = {
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
rules : string;
|
||||
input : string option;
|
||||
prerequisites : string option;
|
||||
prerequisitesStart : string option;
|
||||
prerequisitesEnd : string option;
|
||||
apValue : string option;
|
||||
apValueAppend : string option;
|
||||
errata : Erratum.list option;
|
||||
}
|
||||
|
||||
let t json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
name = json |> field "name" Shared.Name.Decode.t;
|
||||
nameInWiki =
|
||||
json |> optionalField "nameInWiki" Shared.NameInLibrary.Decode.t;
|
||||
rules = json |> field "rules" Shared.Rules.Decode.t;
|
||||
input = json |> optionalField "input" Shared.Input.Decode.t;
|
||||
prerequisites =
|
||||
json
|
||||
|> optionalField "prerequisites"
|
||||
Shared.PrerequisitesReplacement.Decode.t;
|
||||
prerequisitesStart =
|
||||
json
|
||||
|> optionalField "prerequisitesStart"
|
||||
Shared.PrerequisitesStart.Decode.t;
|
||||
prerequisitesEnd =
|
||||
json
|
||||
|> optionalField "prerequisitesEnd"
|
||||
Shared.PrerequisitesEnd.Decode.t;
|
||||
apValue =
|
||||
json |> optionalField "apValue" Shared.ApValueReplacement.Decode.t;
|
||||
apValueAppend =
|
||||
json
|
||||
|> optionalField "apValueAppend" Shared.ApValueAppend.Decode.t;
|
||||
errata = json |> optionalField "errata" Erratum.Decode.list;
|
||||
}
|
||||
|
||||
let pred _ = true
|
||||
end
|
||||
|
||||
type multilingual = {
|
||||
id : int;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
selectOptions : SelectOption.Decode.multilingual option;
|
||||
prerequisites :
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode.multilingual;
|
||||
apValue : Shared.ApValue.t option;
|
||||
src : PublicationRef.Decode.multilingual list;
|
||||
translations : Translation.t Json_Decode_TranslationMap.partial;
|
||||
}
|
||||
|
||||
let multilingual decodeTranslations json =
|
||||
Json_Decode_Strict.
|
||||
{
|
||||
id = json |> field "id" int;
|
||||
levels = json |> optionalField "levels" int;
|
||||
max = json |> optionalField "max" int;
|
||||
selectOptions =
|
||||
json
|
||||
|> optionalField "selectOptions" SelectOption.Decode.multilingual;
|
||||
prerequisites =
|
||||
json
|
||||
|> field "prerequisites"
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.multilingual;
|
||||
apValue = json |> optionalField "apValue" Shared.ApValue.Decode.t;
|
||||
src = json |> field "src" PublicationRef.Decode.multilingualList;
|
||||
translations = json |> field "translations" decodeTranslations;
|
||||
}
|
||||
|
||||
let make ~resolveSelectOptions langs (multilingual : multilingual)
|
||||
(translation : Translation.t) =
|
||||
let src =
|
||||
PublicationRef.Decode.resolveTranslationsList langs multilingual.src
|
||||
in
|
||||
let errata = translation.errata |> Ley_Option.fromOption [] in
|
||||
Some
|
||||
( multilingual.id,
|
||||
{
|
||||
id = multilingual.id;
|
||||
name = translation.name;
|
||||
nameInWiki = translation.nameInWiki;
|
||||
levels = multilingual.levels;
|
||||
max = multilingual.max;
|
||||
rules = translation.rules;
|
||||
selectOptions =
|
||||
multilingual.selectOptions
|
||||
|> Ley_Option.option SelectOption.Map.empty
|
||||
(resolveSelectOptions ~src ~errata langs);
|
||||
input = translation.input;
|
||||
prerequisites =
|
||||
Prerequisite.Collection.AdvantageDisadvantage.Decode
|
||||
.resolveTranslations langs multilingual.prerequisites;
|
||||
prerequisitesText = translation.prerequisites;
|
||||
prerequisitesTextStart = translation.prerequisitesStart;
|
||||
prerequisitesTextEnd = translation.prerequisitesEnd;
|
||||
apValue = multilingual.apValue;
|
||||
apValueText = translation.apValue;
|
||||
apValueTextAppend = translation.apValueAppend;
|
||||
src;
|
||||
errata;
|
||||
} )
|
||||
|
||||
module Accessors = struct
|
||||
let translations x = x.translations
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
module Dynamic = Activatable_Dynamic.Make (struct
|
||||
type static = Static.t
|
||||
end)
|
||||
@@ -0,0 +1,27 @@
|
||||
module Static : sig
|
||||
type t = {
|
||||
id : int;
|
||||
name : string;
|
||||
nameInWiki : string option;
|
||||
levels : int option;
|
||||
max : int option;
|
||||
rules : string;
|
||||
selectOptions : SelectOption.map;
|
||||
input : string option;
|
||||
prerequisites : Prerequisite.Collection.AdvantageDisadvantage.t;
|
||||
prerequisitesText : string option;
|
||||
prerequisitesTextStart : string option;
|
||||
prerequisitesTextEnd : string option;
|
||||
apValue : Activatable_Shared.ApValue.t option;
|
||||
apValueText : string option;
|
||||
apValueTextAppend : string option;
|
||||
src : PublicationRef.list;
|
||||
errata : Erratum.list;
|
||||
}
|
||||
|
||||
module Decode : sig
|
||||
val assoc : t Activatable_Shared.Decode.decodeAssoc
|
||||
end
|
||||
end
|
||||
|
||||
module Dynamic : Activatable_Dynamic.S with type static = Static.t
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user