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 |
@@ -0,0 +1,3 @@
|
||||
src/App/Utilities/YAML/Schema/
|
||||
*.js
|
||||
*.gen.tsx
|
||||
+484
@@ -0,0 +1,484 @@
|
||||
plugins:
|
||||
- "@typescript-eslint"
|
||||
- eslint-plugin-react
|
||||
- eslint-plugin-react-hooks
|
||||
parser: "@typescript-eslint/parser"
|
||||
parserOptions:
|
||||
ecmaVersion: 2020
|
||||
sourceType: module
|
||||
project: ./tsconfig.json
|
||||
ecmaFeatures:
|
||||
jsx: true
|
||||
env:
|
||||
commonjs: true
|
||||
node: true
|
||||
es6: true
|
||||
browser: true
|
||||
rules:
|
||||
# Possible errors
|
||||
for-direction: 2
|
||||
getter-return: 2
|
||||
no-async-promise-executor: 1
|
||||
no-compare-neg-zero: 1
|
||||
no-cond-assign: 2
|
||||
no-constant-condition: 2
|
||||
no-control-regex: 2
|
||||
no-debugger: 2
|
||||
no-duplicate-case: 2
|
||||
no-empty-character-class: 2
|
||||
no-ex-assign: 2
|
||||
no-inner-declarations: 1
|
||||
no-invalid-regexp: 2
|
||||
no-irregular-whitespace: 2
|
||||
no-misleading-character-class: 2
|
||||
no-prototype-builtins: 2
|
||||
no-regex-spaces: 2
|
||||
no-sparse-arrays: 2
|
||||
no-template-curly-in-string: 2
|
||||
no-unreachable: 2
|
||||
no-unsafe-finally: 2
|
||||
no-unsafe-negation: 2
|
||||
require-atomic-updates: 2
|
||||
use-isnan: 2
|
||||
|
||||
# Best Practices
|
||||
accessor-pairs: 2
|
||||
class-methods-use-this: 1
|
||||
# complexity: 2
|
||||
curly:
|
||||
- 2
|
||||
- all
|
||||
default-case: 2
|
||||
dot-location:
|
||||
- 2
|
||||
- property
|
||||
# dot-notation:
|
||||
# - 2
|
||||
# - allowKeywords: false
|
||||
eqeqeq: 2
|
||||
guard-for-in: 2
|
||||
no-alert: 2
|
||||
no-caller: 2
|
||||
no-case-declarations: 2
|
||||
no-div-regex: 2
|
||||
# no-else-return:
|
||||
# - 2
|
||||
# - allowElseIf: true
|
||||
# no-empty-function: 2
|
||||
no-empty-pattern: 2
|
||||
no-eq-null: 2
|
||||
no-eval: 2
|
||||
no-extend-native: 2
|
||||
no-extra-bind: 2
|
||||
no-extra-label: 2
|
||||
no-fallthrough: 2
|
||||
no-floating-decimal: 2
|
||||
no-global-assign: 2
|
||||
no-implicit-coercion:
|
||||
- 2
|
||||
- allow:
|
||||
- "!!"
|
||||
no-implied-eval: 2
|
||||
# no-invalid-this: 2
|
||||
no-iterator: 2
|
||||
no-labels: 2
|
||||
no-lone-blocks: 2
|
||||
no-loop-func: 2
|
||||
no-multi-spaces: 2
|
||||
no-multi-str: 2
|
||||
no-new: 2
|
||||
no-new-func: 2
|
||||
no-new-wrappers: 2
|
||||
no-octal: 2
|
||||
no-octal-escape: 2
|
||||
no-param-reassign: 2
|
||||
no-proto: 2
|
||||
no-redeclare: 0
|
||||
no-return-assign: 2
|
||||
no-script-url: 2
|
||||
no-self-assign: 2
|
||||
no-self-compare: 2
|
||||
no-throw-literal: 2
|
||||
no-unmodified-loop-condition: 2
|
||||
no-unused-labels: 2
|
||||
no-useless-call: 2
|
||||
no-useless-catch: 2
|
||||
no-useless-concat: 2
|
||||
no-useless-escape: 2
|
||||
no-useless-return: 2
|
||||
no-void: 2
|
||||
no-warning-comments: 1
|
||||
no-with: 2
|
||||
prefer-named-capture-group: 1
|
||||
prefer-promise-reject-errors: 2
|
||||
radix: 2
|
||||
require-unicode-regexp: 2
|
||||
wrap-iife: 2
|
||||
yoda: 2
|
||||
|
||||
# Variables
|
||||
init-declarations: 2
|
||||
no-delete-var: 2
|
||||
no-label-var: 2
|
||||
no-shadow: 0
|
||||
no-shadow-restricted-names: 2
|
||||
# no-undef: 2
|
||||
|
||||
# Stylistic Issues
|
||||
array-bracket-spacing:
|
||||
- 2
|
||||
- always
|
||||
array-element-newline:
|
||||
- 2
|
||||
- consistent
|
||||
block-spacing: 2
|
||||
# capitalized-comments: 2
|
||||
comma-dangle:
|
||||
- 2
|
||||
- arrays: always-multiline
|
||||
objects: always-multiline
|
||||
imports: always-multiline
|
||||
exports: always-multiline
|
||||
functions: only-multiline
|
||||
comma-style: 2
|
||||
computed-property-spacing: 2
|
||||
eol-last: 2
|
||||
func-name-matching: 2
|
||||
func-names: 2
|
||||
func-style:
|
||||
- 2
|
||||
- declaration
|
||||
- allowArrowFunctions: true
|
||||
function-call-argument-newline:
|
||||
- 2
|
||||
- consistent
|
||||
function-paren-newline:
|
||||
- 2
|
||||
- consistent
|
||||
jsx-quotes: 2
|
||||
key-spacing: 2
|
||||
keyword-spacing: 2
|
||||
line-comment-position: 2
|
||||
lines-around-comment:
|
||||
- 0
|
||||
# requires an empty line before block comments
|
||||
- beforeBlockComment: true
|
||||
# requires an empty line after block comments
|
||||
afterBlockComment: false
|
||||
# requires an empty line before line comments
|
||||
beforeLineComment: true
|
||||
# requires an empty line after line comments
|
||||
afterLineComment: false
|
||||
# allows comments to appear at the start of block statements
|
||||
allowBlockStart: true
|
||||
# allows comments to appear at the end of block statements
|
||||
allowBlockEnd: false
|
||||
# allows comments to appear at the start of object literals
|
||||
allowObjectStart: true
|
||||
# allows comments to appear at the end of object literals
|
||||
allowObjectEnd: false
|
||||
# allows comments to appear at the start of array literals
|
||||
allowArrayStart: true
|
||||
# allows comments to appear at the end of array literals
|
||||
allowArrayEnd: false
|
||||
# allows comments to appear at the start of classes
|
||||
allowClassStart: true
|
||||
# allows comments to appear at the end of classes
|
||||
allowClassEnd: false
|
||||
ignorePattern: "^ *@"
|
||||
lines-between-class-members: 2
|
||||
max-len:
|
||||
- 2
|
||||
- code: 100
|
||||
ignoreComments: true
|
||||
ignoreTemplateLiterals: true
|
||||
ignorePattern: "^import |^export \\{(.*?)\\}|^ \\* |^ *it ?\\(\"\\w+"
|
||||
max-statements-per-line: [2, { max: 2 }]
|
||||
multiline-ternary:
|
||||
- 2
|
||||
- always-multiline
|
||||
new-parens: 2
|
||||
newline-per-chained-call:
|
||||
- 2
|
||||
- ignoreChainWithDepth: 2
|
||||
no-bitwise: 2
|
||||
no-continue: 2
|
||||
no-inline-comments: 2
|
||||
no-lonely-if: 2
|
||||
no-mixed-operators:
|
||||
- 2
|
||||
- groups:
|
||||
- ["&", "|", "^", "~", "<<", ">>", ">>>"]
|
||||
- ["==", "!=", "===", "!==", ">", ">=", "<", "<="]
|
||||
- ["&&", "||"]
|
||||
- ["in", "instanceof"]
|
||||
allowSamePrecedence: true
|
||||
no-mixed-spaces-and-tabs: 2
|
||||
no-multi-assign: 2
|
||||
no-multiple-empty-lines: 2
|
||||
no-negated-condition: 2
|
||||
no-new-object: 2
|
||||
no-tabs: 2
|
||||
no-trailing-spaces: 2
|
||||
no-unneeded-ternary: 2
|
||||
nonblock-statement-body-position: 2
|
||||
# object-curly-newline:
|
||||
# - 2
|
||||
# - multiline: true
|
||||
object-curly-spacing:
|
||||
- 2
|
||||
- always
|
||||
operator-assignment: 2
|
||||
operator-linebreak:
|
||||
- 2
|
||||
- before
|
||||
- overrides:
|
||||
"=": ignore
|
||||
padded-blocks:
|
||||
- 2
|
||||
- blocks: never
|
||||
classes: never
|
||||
switches: never
|
||||
padding-line-between-statements:
|
||||
- 2
|
||||
- blankLine: always
|
||||
prev: "*"
|
||||
next: return
|
||||
prefer-object-spread: 2
|
||||
quote-props:
|
||||
- 2
|
||||
- as-needed
|
||||
semi-spacing: 2
|
||||
semi-style: 2
|
||||
space-before-blocks: 2
|
||||
space-in-parens: 2
|
||||
space-infix-ops:
|
||||
- 2
|
||||
- int32Hint: true
|
||||
space-unary-ops: 2
|
||||
spaced-comment: 2
|
||||
switch-colon-spacing: 2
|
||||
template-tag-spacing:
|
||||
- 2
|
||||
- always
|
||||
|
||||
# ECMAScript 6
|
||||
arrow-body-style: 2
|
||||
arrow-parens:
|
||||
- 2
|
||||
- as-needed
|
||||
arrow-spacing: 2
|
||||
constructor-super: 2
|
||||
generator-star-spacing:
|
||||
- 2
|
||||
- after
|
||||
no-class-assign: 2
|
||||
no-new-symbol: 2
|
||||
no-this-before-super: 2
|
||||
no-useless-computed-key: 2
|
||||
no-useless-rename: 2
|
||||
no-var: 2
|
||||
object-shorthand: 2
|
||||
prefer-arrow-callback: 2
|
||||
prefer-const: 2
|
||||
prefer-destructuring:
|
||||
- 2
|
||||
- object: true
|
||||
array: false
|
||||
prefer-numeric-literals: 2
|
||||
prefer-rest-params: 2
|
||||
prefer-spread: 2
|
||||
prefer-template: 2
|
||||
require-yield: 2
|
||||
rest-spread-spacing: 2
|
||||
# sort-imports:
|
||||
# - 2
|
||||
# - ignoreCase: true
|
||||
# ignoreMemberSort: true
|
||||
symbol-description: 2
|
||||
template-curly-spacing: 2
|
||||
yield-star-spacing:
|
||||
- 2
|
||||
- after
|
||||
|
||||
# TypeScript
|
||||
"@typescript-eslint/adjacent-overload-signatures": 2
|
||||
"@typescript-eslint/array-type": 2
|
||||
"@typescript-eslint/await-thenable": 2
|
||||
# "@typescript-eslint/class-name-casing": 2
|
||||
"@typescript-eslint/consistent-type-assertions": 2
|
||||
"@typescript-eslint/member-delimiter-style":
|
||||
- 2
|
||||
- multiline:
|
||||
delimiter: none
|
||||
singleline:
|
||||
delimiter: semi
|
||||
requireLast: false
|
||||
"@typescript-eslint/naming-convention":
|
||||
- error
|
||||
- selector: interface
|
||||
format:
|
||||
- PascalCase
|
||||
custom:
|
||||
regex: "^I[A-Z]"
|
||||
match: false
|
||||
"@typescript-eslint/no-floating-promises": 2
|
||||
"@typescript-eslint/no-for-in-array": 2
|
||||
"@typescript-eslint/no-inferrable-types": 2
|
||||
"@typescript-eslint/no-misused-new": 2
|
||||
"@typescript-eslint/no-misused-promises": 2
|
||||
"@typescript-eslint/no-this-alias": 2
|
||||
"@typescript-eslint/no-unnecessary-type-arguments": 2
|
||||
"@typescript-eslint/no-unnecessary-type-assertion": 2
|
||||
"@typescript-eslint/prefer-for-of": 2
|
||||
"@typescript-eslint/prefer-includes": 2
|
||||
"@typescript-eslint/prefer-readonly": 2
|
||||
"@typescript-eslint/promise-function-async": 2
|
||||
# "@typescript-eslint/quotes": 2
|
||||
"@typescript-eslint/require-array-sort-compare": 2
|
||||
"@typescript-eslint/require-await": 2
|
||||
"@typescript-eslint/restrict-plus-operands": 2
|
||||
# "@typescript-eslint/semi":
|
||||
# - 2
|
||||
# - never
|
||||
"@typescript-eslint/strict-boolean-expressions": 2
|
||||
"@typescript-eslint/triple-slash-reference": 2
|
||||
"@typescript-eslint/type-annotation-spacing": 2
|
||||
# "@typescript-eslint/unbound-method": 2
|
||||
"@typescript-eslint/unified-signatures": 2
|
||||
|
||||
# TypeScript Extension
|
||||
brace-style: 0
|
||||
"@typescript-eslint/brace-style":
|
||||
- 2
|
||||
- stroustrup
|
||||
comma-spacing: 0
|
||||
"@typescript-eslint/comma-spacing": 2
|
||||
func-call-spacing: 0
|
||||
"@typescript-eslint/func-call-spacing":
|
||||
- 2
|
||||
- always
|
||||
- allowNewlines: true
|
||||
indent: 0
|
||||
"@typescript-eslint/indent": 0
|
||||
no-array-constructor: 0
|
||||
"@typescript-eslint/no-array-constructor": 2
|
||||
no-extra-parens: 0
|
||||
"@typescript-eslint/no-extra-parens": 0
|
||||
no-extra-semi: 0
|
||||
"@typescript-eslint/no-extra-semi": 2
|
||||
no-unused-expressions: 0
|
||||
"@typescript-eslint/no-redeclare": 2
|
||||
"@typescript-eslint/no-shadow": 2
|
||||
"@typescript-eslint/no-unused-expressions": 2
|
||||
"@typescript-eslint/no-unused-vars-experimental":
|
||||
- 2
|
||||
- ignoredNamesRegex: "^_"
|
||||
"@typescript-eslint/no-use-before-define": 2
|
||||
no-useless-constructor: 0
|
||||
"@typescript-eslint/no-useless-constructor": 2
|
||||
quotes: 0
|
||||
"@typescript-eslint/quotes":
|
||||
- 2
|
||||
- double
|
||||
- avoidEscape: true
|
||||
allowTemplateLiterals: true
|
||||
no-return-await: 0
|
||||
"@typescript-eslint/return-await": 2
|
||||
semi: 0
|
||||
"@typescript-eslint/semi":
|
||||
- 2
|
||||
- never
|
||||
- beforeStatementContinuationChars: never
|
||||
space-before-function-paren: 0
|
||||
"@typescript-eslint/space-before-function-paren": 2
|
||||
|
||||
# React
|
||||
react/boolean-prop-naming: 2
|
||||
react/destructuring-assignment: 2
|
||||
react/no-access-state-in-setstate: 2
|
||||
react/no-array-index-key: 2
|
||||
react/no-children-prop: 2
|
||||
react/no-danger: 2
|
||||
react/no-danger-with-children: 2
|
||||
react/no-deprecated: 2
|
||||
react/no-did-mount-set-state: 2
|
||||
react/no-did-update-set-state: 1
|
||||
react/no-direct-mutation-state: 2
|
||||
react/no-find-dom-node: 2
|
||||
react/no-is-mounted: 2
|
||||
# react/no-multi-comp: 2
|
||||
react/no-redundant-should-component-update: 2
|
||||
react/no-render-return-value: 2
|
||||
react/no-typos: 2
|
||||
react/no-string-refs: 2
|
||||
react/no-this-in-sfc: 2
|
||||
react/no-unescaped-entities: 2
|
||||
react/no-unsafe: 2
|
||||
react/no-unused-prop-types: 2
|
||||
react/no-unused-state: 2
|
||||
react/no-will-update-set-state: 2
|
||||
react/prefer-es6-class: 2
|
||||
react/prefer-stateless-function: 2
|
||||
react/react-in-jsx-scope: 2
|
||||
react/require-render-return: 2
|
||||
react/self-closing-comp: 2
|
||||
react/state-in-constructor:
|
||||
- 2
|
||||
- never
|
||||
react/style-prop-object: 2
|
||||
react/void-dom-elements-no-children: 2
|
||||
react/jsx-boolean-value:
|
||||
- 2
|
||||
- never
|
||||
react/jsx-child-element-spacing: 2
|
||||
react/jsx-closing-bracket-location:
|
||||
- 2
|
||||
- props-aligned
|
||||
react/jsx-closing-tag-location: 2
|
||||
react/jsx-curly-newline: 2
|
||||
react/jsx-curly-spacing: 2
|
||||
react/jsx-equals-spacing: 2
|
||||
react/jsx-first-prop-new-line: 2
|
||||
react/jsx-handler-names: 2
|
||||
# react/jsx-indent:
|
||||
# - 2
|
||||
# - 2
|
||||
react/jsx-indent-props:
|
||||
- 2
|
||||
- 2
|
||||
react/jsx-max-props-per-line:
|
||||
- 2
|
||||
- maximum: 3
|
||||
react/jsx-no-bind: 2
|
||||
react/jsx-no-comment-textnodes: 2
|
||||
react/jsx-no-literals: 2
|
||||
react/jsx-no-target-blank: 2
|
||||
react/jsx-one-expression-per-line:
|
||||
- 2
|
||||
- allow: single-child
|
||||
react/jsx-curly-brace-presence:
|
||||
- 2
|
||||
- props: never
|
||||
children: ignore
|
||||
react/jsx-fragments: 2
|
||||
react/jsx-pascal-case: 2
|
||||
react/jsx-props-no-multi-spaces: 2
|
||||
react/jsx-props-no-spreading: 2
|
||||
react/jsx-tag-spacing: 2
|
||||
react/jsx-wrap-multilines:
|
||||
- 2
|
||||
- declaration: parens-new-line
|
||||
assignment: parens-new-line
|
||||
return: parens-new-line
|
||||
arrow: parens-new-line
|
||||
condition: parens-new-line
|
||||
logical: parens-new-line
|
||||
prop: ignore
|
||||
|
||||
# React Hooks
|
||||
react-hooks/rules-of-hooks: 2
|
||||
react-hooks/exhaustive-deps: 2
|
||||
settings:
|
||||
react:
|
||||
version: detect
|
||||
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: optolith # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
|
||||
- OS: [e.g. Windows 10]
|
||||
- Version: [e.g. 1.1.0, 1.3.0-alpha.5]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,73 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a report to help improve Optolith
|
||||
labels:
|
||||
- Bug
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: agreements
|
||||
attributes:
|
||||
label: Please confirm the following
|
||||
options:
|
||||
- label: I checked the [current issues](https://github.com/elyukai/optolith-client/issues) for duplicate problems.
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: to-reproduce
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: |
|
||||
Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: Which operating systems have you used? You may select more than one.
|
||||
multiple: true
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which Optolith version have you used?
|
||||
placeholder: 1.1.0, 1.3.0-alpha.5, …
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -7,24 +7,14 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Is your feature request related to a problem?
|
||||
|
||||
A clear and concise description of what the problem is. E.g. _I'm always frustrated when [...]_
|
||||
|
||||
|
||||
|
||||
## Describe the solution you'd like
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
|
||||
|
||||
## Describe alternatives
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
|
||||
|
||||
## Additional context
|
||||
|
||||
**Additional context**
|
||||
> Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -7,30 +7,21 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
**Sources**
|
||||
> List here what is included in this part of the book to implement. E.g., for special abilities, you need to list every source of entries from the book. Example (in German):
|
||||
>
|
||||
> - S. 133–150 (Berufsgeheimnisse)
|
||||
> - [ ] S. 133 (Berufsgeheimnisse)
|
||||
> - [ ] S. 165-170 (Zauber- und Liturgiestile)
|
||||
> - [ ] S. 183 (Stabzauber)
|
||||
> - [ ] S. 195 (Zeremonialgegenstand Widderkeule)
|
||||
|
||||
- S. X (Section/Chapter Name)
|
||||
**Tables**
|
||||
> Delete Univ and change locale if necessary. Add specific entries if they could not be resolved.
|
||||
|
||||
- [ ] Univ
|
||||
- [ ] L10n de-DE
|
||||
|
||||
|
||||
## Special Notes
|
||||
|
||||
> List here any special hints to implement the sources listed above.
|
||||
>
|
||||
> - …
|
||||
|
||||
—
|
||||
|
||||
|
||||
|
||||
## Special Implementations
|
||||
|
||||
> List here what cannot be solved only using the database. Example (in German):
|
||||
**Code**
|
||||
> List here what cannot be solved only using the tables. Example (in German):
|
||||
>
|
||||
> - [ ] Voraussetzungen Vorliebe I-II
|
||||
|
||||
—
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Publication ambiguity
|
||||
about: An ambiguity in an official publication
|
||||
title: ''
|
||||
labels: 'Ulisses: Question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Source(s)
|
||||
|
||||
Please complete the following information:
|
||||
|
||||
- Book: [e.g. Magic of Aventuria I]
|
||||
- Printing: [e.g. 1st]
|
||||
- Page(s): [e.g. 199]
|
||||
- Area: [e.g. Profession Black Mage (Demirion Ophenos's Student), AP value]
|
||||
|
||||
|
||||
|
||||
## Describe the ambiguity
|
||||
|
||||
A clear and concise description of what the ambiguity is.
|
||||
@@ -7,33 +7,21 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Source (please complete the following information):
|
||||
**Source (please complete the following information):**
|
||||
|
||||
- Book: [e.g. Magic of Aventuria I]
|
||||
- Printing: [e.g. 1st]
|
||||
- Page(s): [e.g. 199]
|
||||
- Area: [e.g. Profession Black Mage (Demirion Ophenos's Student), AP value]
|
||||
|
||||
|
||||
|
||||
## Describe the bug
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
|
||||
|
||||
## Expected text
|
||||
|
||||
**Expected text**
|
||||
A clear and concise description of what the text *should* be instead.
|
||||
|
||||
|
||||
|
||||
## Optolith Version:
|
||||
|
||||
**Optolith Version:**
|
||||
If applicable, the exact version of Optolith where you experience the bug, e.g. 1.2.0, 1.3.0-alpha.5.
|
||||
|
||||
|
||||
|
||||
## Additional context
|
||||
|
||||
**Additional context**
|
||||
If needed, add any other context about the problem here.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Question
|
||||
about: I have a question.
|
||||
title: ''
|
||||
labels: Question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- develop-v2
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
- develop-v2
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Compile TypeScript files
|
||||
run: npm run ts:build
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
linting:
|
||||
name: Linting
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Run linter
|
||||
run: npm run lint
|
||||
@@ -3,7 +3,7 @@ name: Prerelease Builds
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+-[a-z]+.[0-9]+
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,56 +12,41 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macOS-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macOS-latest
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
osName: Linux
|
||||
- os: windows-latest
|
||||
osName: Windows
|
||||
- os: macOS-latest
|
||||
osName: macOS
|
||||
- os: ubuntu-latest
|
||||
osName: Linux
|
||||
- os: windows-latest
|
||||
osName: Windows
|
||||
- os: macOS-latest
|
||||
osName: macOS
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Compile JavaScript files
|
||||
run: npm run js:build
|
||||
- name: Build database cache
|
||||
run: npm run buildCache
|
||||
working-directory: ./src/database/contents
|
||||
- name: Build macOS application
|
||||
if: runner.os == 'macOS'
|
||||
run: node ./deploy/build.js --prerelease
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
TEAMID: ${{ secrets.TEAMID }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Build Linux application
|
||||
if: runner.os == 'Linux'
|
||||
run: node ./deploy/build.js --prerelease
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Build Windows application
|
||||
if: runner.os == 'Windows'
|
||||
run: node ./deploy/build.js --prerelease
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Deploy
|
||||
run: node ./deploy/deploy.js --prerelease
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
ROOT: ${{ secrets.ROOT }}
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Build ReasonML files
|
||||
run: npm run re:build
|
||||
- name: Build TypeScript files
|
||||
run: npm run js:build
|
||||
- name: Build Sass files
|
||||
run: npm run css:build
|
||||
- name: Build
|
||||
run: node ./deploy/build.js prerelease
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Deploy
|
||||
run: node ./deploy/deploy.js prerelease
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
ROOT: ${{ secrets.ROOT }}
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Stable Builds
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,56 +12,41 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macOS-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macOS-latest
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
osName: Linux
|
||||
- os: windows-latest
|
||||
osName: Windows
|
||||
- os: macOS-latest
|
||||
osName: macOS
|
||||
- os: ubuntu-latest
|
||||
osName: Linux
|
||||
- os: windows-latest
|
||||
osName: Windows
|
||||
- os: macOS-latest
|
||||
osName: macOS
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Compile JavaScript files
|
||||
run: npm run js:build
|
||||
- name: Build database cache
|
||||
run: npm run buildCache
|
||||
working-directory: ./src/database/contents
|
||||
- name: Build macOS application
|
||||
if: runner.os == 'macOS'
|
||||
run: node ./deploy/build.js --stable
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
TEAMID: ${{ secrets.TEAMID }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Build Linux application
|
||||
if: runner.os == 'Linux'
|
||||
run: node ./deploy/build.js --stable
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Build Windows application
|
||||
if: runner.os == 'Windows'
|
||||
run: node ./deploy/build.js --stable
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Deploy
|
||||
run: node ./deploy/deploy.js --stable
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
ROOT: ${{ secrets.ROOT }}
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Build ReasonML files
|
||||
run: npm run re:build
|
||||
- name: Build TypeScript files
|
||||
run: npm run js:build
|
||||
- name: Build Sass files
|
||||
run: npm run css:build
|
||||
- name: Build
|
||||
run: node ./deploy/build.js stable
|
||||
env:
|
||||
UPDATE_URL: ${{ secrets.UPDATE_URL }}
|
||||
- name: Deploy
|
||||
run: node ./deploy/deploy.js stable
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PASSWORD: ${{ secrets.PASSWORD }}
|
||||
ROOT: ${{ secrets.ROOT }}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- run: npm run re:build
|
||||
- run: npm run ts:build
|
||||
- run: npm test
|
||||
env:
|
||||
CI: true
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.ACCESS_TOKEN }}
|
||||
submodules: true
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- run: npm run lint
|
||||
env:
|
||||
CI: true
|
||||
+4
-2
@@ -1,6 +1,5 @@
|
||||
.DS_Store
|
||||
_opam
|
||||
node_modules/
|
||||
/.webpack/**/*
|
||||
npm-debug.log*
|
||||
.vs/
|
||||
/data/
|
||||
@@ -18,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 = src/database/contents
|
||||
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
|
||||
@@ -1,5 +0,0 @@
|
||||
arrowParens: avoid
|
||||
printWidth: 100
|
||||
semi: false
|
||||
tabWidth: 2
|
||||
trailingComma: all
|
||||
Vendored
+3
-3
@@ -3,7 +3,7 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Electron: Debug Main",
|
||||
"type": "pwa-node",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||
@@ -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
-8
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"typescript.preferences.quoteStyle": "double",
|
||||
"javascript.preferences.quoteStyle": "double",
|
||||
"files.exclude": {
|
||||
@@ -15,14 +15,13 @@
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.lintTask.options": "\"src/**\"",
|
||||
"eslint.experimental.useFlatConfig": 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,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
"[ocaml]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[rescript]": {
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+12
-48
@@ -5,22 +5,20 @@
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"type": "shell",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "start",
|
||||
"command": "npm start"
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"command": "npm test",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
"label": "re:watch",
|
||||
"type": "shell",
|
||||
"command": "npm run re:watch",
|
||||
"group": "build",
|
||||
"isBackground": true,
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "tsc watch",
|
||||
"label": "ts:watch",
|
||||
"type": "typescript",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"option": "watch",
|
||||
@@ -30,48 +28,14 @@
|
||||
],
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"group": "watch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "webpack watch",
|
||||
"command": "npm run js:watch",
|
||||
"isBackground": true,
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"group": "watch"
|
||||
"group": "watchers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"dependsOn": ["tsc watch", "webpack watch"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "create prerelease",
|
||||
"command": "npm run release:pre",
|
||||
"dependsOn": ["re:watch", "ts:watch"],
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "build database cache",
|
||||
"command": "npm run buildCache",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src/database/contents"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "create release",
|
||||
"command": "npm run release:main",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "git push --follow-tags",
|
||||
"command": "git push --follow-tags",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
Lukas Obermann <lukas.obermann@icloud.com>
|
||||
Lukas Obermann <lukas.obermann@outlook.de>
|
||||
|
||||
-277
@@ -2,283 +2,6 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [1.5.2-alpha.4](https://github.com/elyukai/optolith-client/compare/v1.5.2-alpha.3...v1.5.2-alpha.4) (2022-07-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improvement cost handling for some traditions ([c52fc26](https://github.com/elyukai/optolith-client/commit/c52fc26a1b1d6cccc1cfec758da8886be966233b))
|
||||
* show improvement cost in tabular list ([c162931](https://github.com/elyukai/optolith-client/commit/c162931e74bce5f4fd1e42de6ebc92c71d15e81a))
|
||||
|
||||
### [1.5.2-alpha.3](https://github.com/elyukai/optolith-client/compare/v1.5.2-alpha.2...v1.5.2-alpha.3) (2022-07-12)
|
||||
|
||||
### [1.5.2-alpha.2](https://github.com/elyukai/optolith-client/compare/v1.5.2-alpha.1...v1.5.2-alpha.2) (2022-07-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* enable auto-update for macOS ([7c6e377](https://github.com/elyukai/optolith-client/commit/7c6e3776921fdc4f13c42ea5c6a4bf5d316856e1))
|
||||
|
||||
### [1.5.2-alpha.1](https://github.com/elyukai/optolith-client/compare/v1.5.1-alpha.2...v1.5.2-alpha.1) (2022-04-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add krallenkettenzauber as placeholder ([c987847](https://github.com/elyukai/optolith-client/commit/c987847c9b192a0528df52da33f55adf90164eaf))
|
||||
|
||||
|
||||
## [1.5.1](https://github.com/elyukai/optolith-client/compare/v1.5.0...v1.5.1) (2022-03-20)
|
||||
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **MapTool:** add links to extension next to export button ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MapTool:** without an avatar no export was possible ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), closes [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** changed export format for MapTool version 1.11.4 ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* **MapTool:** Add missing Italian translations for export ([3f87dca](https://github.com/elyukai/optolith-client/commit/3f87dca910ce5229c7fd5378ba5f4177cbf431ac)), closes [#1350](https://github.com/elyukai/optolith-client/issues/1350)
|
||||
* Advantage *Inspire Confidence* could be bought with an active *Incompetent* in a social skill ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), closes [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Strikethrough prerequisites were rendered with surrounding tildes instead ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), closes [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
### [1.5.1-alpha.2](https://github.com/elyukai/optolith-client/compare/v1.5.1-alpha.1...v1.5.1-alpha.2) (2022-02-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **maptool:** add project links ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* advantage Inspire Confidence could be bought with social Incompetence ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), closes [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
|
||||
### [1.5.1-alpha.1](https://github.com/elyukai/optolith-client/compare/v1.5.1-alpha.0...v1.5.1-alpha.1) (2022-02-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **maptool:** no export possible without an avatar ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), closes [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
|
||||
### [1.5.1-alpha.0](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.9...v1.5.1-alpha.0) (2022-02-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add missing Italian translations for MapTool export ([3f87dca](https://github.com/elyukai/optolith-client/commit/3f87dca910ce5229c7fd5378ba5f4177cbf431ac)), closes [#1350](https://github.com/elyukai/optolith-client/issues/1350)
|
||||
* add more action types that should be ignored for undo history ([d2eca82](https://github.com/elyukai/optolith-client/commit/d2eca82ea5fd1d65f234363d2700c80f37afe653))
|
||||
* **maptool:** format change for 1.11.4 ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* markdown parser ignores strikethrough ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), closes [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## [1.5.0](https://github.com/elyukai/optolith-client/compare/v1.4.2...v1.5.0) (2022-02-09)
|
||||
|
||||
### Features
|
||||
|
||||
* MapTool export ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Portuguese translations ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Native Apple M1 support ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **English:** Kilogram input hint for weight in item dialog ([bd9fc1b](https://github.com/elyukai/optolith-client/commit/bd9fc1badc8a408495bd7c1594338b1a5fe6df11))
|
||||
* Security ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) and others)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **German/English:** Hit Zone Armor character sheet had left margin ([e637136](https://github.com/elyukai/optolith-client/commit/e63713687eff9b825a79d159fb8b44dc26f64ffe), [#788](https://github.com/elyukai/optolith-client/issues/788))
|
||||
* Additional LE set a minimum required value of STR instead of CON ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Two-handed (2H) and improvised (i) weapon indicators were missing on character sheet ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Brawling weapons had no parry on character sheet ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* An entry that depends on a single activation of another entry disables removing of all activations of that entry if multiple activations are present ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Entries affecting spells to not be unfamiliar could be removed if Spell Enhancements were bought ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* The equipment info area may have crashed ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Professions that granted higher Combat Technique Ratings than what the selected Experience Level allowed were not filtered ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* **German:** Special ability Kernschuss had misplaced minus sign in penalty ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#1080](https://github.com/elyukai/optolith-client/issues/1080))
|
||||
* **German:** Wrong page number in description of Albernisches Entermesser and Harbener Säbel ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#1081](https://github.com/elyukai/optolith-client/issues/1081))
|
||||
* **German:** Spell Zauberschnurren had mixed-up QL descriptions in spell description ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#846](https://github.com/elyukai/optolith-client/issues/846) [#1122](https://github.com/elyukai/optolith-client/issues/1122))
|
||||
* **German:** Schlachtermesser and Fleischerbeil were not marked as improvised ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#808](https://github.com/elyukai/optolith-client/issues/808))
|
||||
* INI/MOV modifiers on armor were handled as penalties ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Language specializations did not always cost 1 AP and could not be activated multiple times for a single language ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Portraits with transparency had a black background ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* **German:** Profession Garether Krieger was missing Swords 12 and Orienting should be 3 instead of 2 ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#792](https://github.com/elyukai/optolith-client/issues/792))
|
||||
* Bornlander cultural package had Prepare Food instead of Leatherworking ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* **Italian:** Some typos in translation of items, languages and scripts ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#832](https://github.com/elyukai/optolith-client/issues/832))
|
||||
* **Italian:** Doubled level range in title of Iron Will special ability ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#850](https://github.com/elyukai/optolith-client/issues/850))
|
||||
* **Italian:** Spells and liturgies displayed different casing and punctuation ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#851](https://github.com/elyukai/optolith-client/issues/851))
|
||||
* **German/English:** Spell Wind Shaping had the wrong traditions and wrong spell enhancements order ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#1139](https://github.com/elyukai/optolith-client/issues/1139))
|
||||
* **German:** Typo in measurements in description of spell Invocatio Minima ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#1224](https://github.com/elyukai/optolith-client/issues/1224))
|
||||
* Primary attribute changes for items were not applied in edit window, only on character sheet ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Professions with due to EL impossible attribute values could be selected ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Summoning spellworks from professions did not register dependencies from prerequisites ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* **Dutch:** Typo in effect of Bliksemblind spell ([c58279c](https://github.com/elyukai/optolith-client/commit/c58279cc95aba5506824510d83aa6ffbc410107a), [#805](https://github.com/elyukai/optolith-client/issues/805))
|
||||
* Weight of Elves was not generated correctly ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* **English:** Gaze into the Essence spell was misspelled as Gase into the Essence ([42891f0](https://github.com/elyukai/optolith-client/commit/42891f070c35b74c49bb2c9e16152789cc8cc7df), [#1180](https://github.com/elyukai/optolith-client/issues/1180))
|
||||
* **German:** Typo in Physiostabilis name ([e40f995](https://github.com/elyukai/optolith-client/commit/e40f995e341dc7fbcb1d9688c4b82a9187c180de), [#1234](https://github.com/elyukai/optolith-client/issues/1234))
|
||||
* **German/English:** Tradition (Druid) referenced a non-existent disadvantage instead of advantage Iron-Attuned Aura ([f8114c2](https://github.com/elyukai/optolith-client/commit/f8114c2f4318967631b71ea3614bf647e7826fa2), [#1237](https://github.com/elyukai/optolith-client/issues/1237))
|
||||
* **German/English:** Hook Dagger was not marked as a parrying weapon ([ed74170](https://github.com/elyukai/optolith-client/commit/ed74170865b2caad8008a345db5c1705e0376bc5), [#1232](https://github.com/elyukai/optolith-client/issues/1232))
|
||||
* **English:** Special ability Deadly Thrust had special ability Crossblock's rules text ([ec9bd71](https://github.com/elyukai/optolith-client/commit/ec9bd71e101abcaf61e84e3da340890e161896ea), [#841](https://github.com/elyukai/optolith-client/issues/841))
|
||||
* **German:** Weapon advantage of Kriegsbogen has wrong DP bonus ([8297455](https://github.com/elyukai/optolith-client/commit/8297455ddbd360035428a4ed244a684e6f1c7e7a), [#1074](https://github.com/elyukai/optolith-client/issues/1074))
|
||||
* **English:** Missing translations for spell enhancements of Heptagramma ([a5df7c8](https://github.com/elyukai/optolith-client/commit/a5df7c863cded391609950fa5b8bf8474ffe2dfd), [#848](https://github.com/elyukai/optolith-client/issues/848))
|
||||
* **German:** Wrong Animal Lore value in profession Spider Witch ([#1250](https://github.com/elyukai/optolith-client/issues/1250))
|
||||
* Attributes on minimum value (8) did not allow a combat technique rating over 6 if the attribute was the combat technique's primary attribute ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* **German/English:** Profession Sharisad got Earthencraft bonus instead of Clothworking bonus ([cd16e53](https://github.com/elyukai/optolith-client/commit/cd16e5314a7f579a5f5f916bb204a15df3220bda), [#1341](https://github.com/elyukai/optolith-client/issues/1341))
|
||||
* **English:** The Hit Zone Armor tab in Belongings was not available, even with Aventuria Armory activated ([383a9c1](https://github.com/elyukai/optolith-client/commit/383a9c166c6d8c5d713f4852f40999c5d8c00efc), [#1328](https://github.com/elyukai/optolith-client/issues/1328))
|
||||
* Too large PDF output when using a sheet background ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* **English:** Weight column in inventory showed wrong weight ([6ba99f2](https://github.com/elyukai/optolith-client/commit/6ba99f2f4610e8a26d1703d978738a907f26bd92), [#1329](https://github.com/elyukai/optolith-client/issues/1329))
|
||||
* Auto-updater did not work in AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* **German:** Special ability Weg der Schreiberin could not be activated ([83b534d](https://github.com/elyukai/optolith-client/commit/83b534d3f9fcd5e63ef7cf482d9dd26f06b5a1a2), [#1233](https://github.com/elyukai/optolith-client/issues/1233))
|
||||
* Some primary attribute prerequisites were not enforced ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Range brackets input fields influenced other bracket fields ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* Requiring only one matching languages and script required all of them instead ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Scripts did not require an active matching language ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Some item properties were not saved ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* **German:** Typo in Tarnele ([8b5eede](https://github.com/elyukai/optolith-client/commit/8b5eedeb372622761480f2511935e8b74bdfcdcd), [#1330](https://github.com/elyukai/optolith-client/issues/1330))
|
||||
* Unfamiliar spell selection in profession was optional ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* **English:** special ability Knuckle Storm's description used Damage Points instead of Brawling Points ([06b57c9](https://github.com/elyukai/optolith-client/commit/06b57c9d15e1ef6f35da69ea46512bb790119b0a), [#1340](https://github.com/elyukai/optolith-client/issues/1340))
|
||||
* Combat techniques did not influence the minimum of their primary attributes ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* Saving a hero cleared the undo history of multiple characters ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* **German:** Spells converted by Imitationszauberei could not use spell enhancements ([e558c1d](https://github.com/elyukai/optolith-client/commit/e558c1d80d1c668f926e4a56049029f989081a45), [#828](https://github.com/elyukai/optolith-client/issues/828))
|
||||
* Widened rows in second column of skills table on character sheet ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
* **German:** Special ability Blattschuss displayed the wrong combat special ability type ([7e31136](https://github.com/elyukai/optolith-client/commit/7e31136083ae62e26c5017f3af2376938bd264c3), [#1331](https://github.com/elyukai/optolith-client/issues/1331))
|
||||
* **German:** Typo in name of Tradition (Illusionist) ([bdb3eb7](https://github.com/elyukai/optolith-client/commit/bdb3eb7804f8464689172f39d5bd8c229140c891), [#1239](https://github.com/elyukai/optolith-client/issues/1239))
|
||||
|
||||
## [1.5.0-alpha.9](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.8...v1.5.0-alpha.9) (2022-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* active animist powers occurred multiple times in addition list ([06eac93](https://github.com/elyukai/optolith-client/commit/06eac932217377b78901a08a1133789d95e8920e)), closes [#794](https://github.com/elyukai/optolith-client/issues/794)
|
||||
* typo in German name of Tradition (Illusionist) ([bdb3eb7](https://github.com/elyukai/optolith-client/commit/bdb3eb7804f8464689172f39d5bd8c229140c891)), closes [#1239](https://github.com/elyukai/optolith-client/issues/1239)
|
||||
|
||||
## [1.5.0-alpha.8](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.7...v1.5.0-alpha.8) (2022-02-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **logs:** add small logging utility function ([8b18b26](https://github.com/elyukai/optolith-client/commit/8b18b26361c1526c93b4ca8906e961d56f85ed49))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* attribute minimum must include combat techniques ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185)), closes [#836](https://github.com/elyukai/optolith-client/issues/836)
|
||||
* **datatypes:** find might return an existing value that is undefined ([232795d](https://github.com/elyukai/optolith-client/commit/232795d12984e1070223bffd60b79367d3560752))
|
||||
* **prerelease:** cult of Levtan not registered as blessed tradition ([1f09060](https://github.com/elyukai/optolith-client/commit/1f090605a4c5404ed1550a0b4048a72dffbb0104))
|
||||
* saving a hero clears the undo history of multiple characters ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9)), closes [#811](https://github.com/elyukai/optolith-client/issues/811)
|
||||
* spells converted by Imitationszauberei cannot use spell enhancements ([e558c1d](https://github.com/elyukai/optolith-client/commit/e558c1d80d1c668f926e4a56049029f989081a45)), closes [#828](https://github.com/elyukai/optolith-client/issues/828)
|
||||
* widened rows in seconds column of skills table on character sheet ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa)), closes [#1342](https://github.com/elyukai/optolith-client/issues/1342)
|
||||
* wrong combat type of special ability Blattschuss ([7e31136](https://github.com/elyukai/optolith-client/commit/7e31136083ae62e26c5017f3af2376938bd264c3)), closes [#1331](https://github.com/elyukai/optolith-client/issues/1331)
|
||||
|
||||
## [1.5.0-alpha.7](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.6...v1.5.0-alpha.7) (2022-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* auto-updater not working in AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4)), closes [#753](https://github.com/elyukai/optolith-client/issues/753)
|
||||
* cannot activate special ability Weg der Schreiberin ([83b534d](https://github.com/elyukai/optolith-client/commit/83b534d3f9fcd5e63ef7cf482d9dd26f06b5a1a2)), closes [#1233](https://github.com/elyukai/optolith-client/issues/1233)
|
||||
* primary attribute prerequisites may not have been added as dependencies ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e)), closes [#1260](https://github.com/elyukai/optolith-client/issues/1260)
|
||||
* range brackets input influences other bracket fields ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af)), closes [#1251](https://github.com/elyukai/optolith-client/issues/1251)
|
||||
* requiring one matching languages and script requires all of them ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a)), closes [#1343](https://github.com/elyukai/optolith-client/issues/1343)
|
||||
* scripts do not require an active matching language ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e)), closes [#1344](https://github.com/elyukai/optolith-client/issues/1344)
|
||||
* some item properties were not saved ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c)), closes [#1252](https://github.com/elyukai/optolith-client/issues/1252)
|
||||
* typo in German name of Tarnele ([8b5eede](https://github.com/elyukai/optolith-client/commit/8b5eedeb372622761480f2511935e8b74bdfcdcd)), closes [#1330](https://github.com/elyukai/optolith-client/issues/1330)
|
||||
* unfamiliar spell selection in profession was optional ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f)), closes [#1315](https://github.com/elyukai/optolith-client/issues/1315)
|
||||
* use Brawling Points in special ability Knuckle Storm description ([06b57c9](https://github.com/elyukai/optolith-client/commit/06b57c9d15e1ef6f35da69ea46512bb790119b0a)), closes [#1340](https://github.com/elyukai/optolith-client/issues/1340)
|
||||
|
||||
## [1.5.0-alpha.6](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.5...v1.5.0-alpha.6) (2022-02-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add kilogram hint to English item dialog ([bd9fc1b](https://github.com/elyukai/optolith-client/commit/bd9fc1badc8a408495bd7c1594338b1a5fe6df11))
|
||||
* add utilities for working with tagged unions ([bba9b92](https://github.com/elyukai/optolith-client/commit/bba9b92e5d50fbb7cad7450b56b95b87e2e9aa4d))
|
||||
* add native Apple M1 support ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* attributes on minimum value do not allow CtR over 6 ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94)), closes [#835](https://github.com/elyukai/optolith-client/issues/835)
|
||||
* profession Sharisad gets Earthencraft bonus instead of Clothworking bonus ([cd16e53](https://github.com/elyukai/optolith-client/commit/cd16e5314a7f579a5f5f916bb204a15df3220bda)), closes [#1341](https://github.com/elyukai/optolith-client/issues/1341)
|
||||
* show Hit Zone Armor tab for English Armory ([383a9c1](https://github.com/elyukai/optolith-client/commit/383a9c166c6d8c5d713f4852f40999c5d8c00efc)), closes [#1328](https://github.com/elyukai/optolith-client/issues/1328)
|
||||
* special ability Tradition (Tsatuara-Anhängerinnen) is in wrong group ([6669352](https://github.com/elyukai/optolith-client/commit/6669352d06c3687a9793eb69ea0018915b865212)), closes [#1137](https://github.com/elyukai/optolith-client/issues/1137)
|
||||
* too large PDF output when using a sheet background ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4)), closes [#1094](https://github.com/elyukai/optolith-client/issues/1094)
|
||||
* weight column in inventory shows wrong weight in English ([6ba99f2](https://github.com/elyukai/optolith-client/commit/6ba99f2f4610e8a26d1703d978738a907f26bd92)), closes [#1329](https://github.com/elyukai/optolith-client/issues/1329)
|
||||
|
||||
## [1.5.0-alpha.5](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.4...v1.5.0-alpha.5) (2022-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* portuguese translations ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* latest TypeScript compilation errors ([6b93c15](https://github.com/elyukai/optolith-client/commit/6b93c15aa02513dcd8ddccfb43cea92370de8290))
|
||||
* missing import ([29c7b4b](https://github.com/elyukai/optolith-client/commit/29c7b4bc39d21e41291ee944c8051b2b10863069))
|
||||
|
||||
## [1.5.0-alpha.4](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.3...v1.5.0-alpha.4) (2021-08-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add XML export for MapTool ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* maptools should provide user interface strings ([b2fb035](https://github.com/elyukai/optolith-client/commit/b2fb0353a9cac409d726e3ad236c0508f230ff06))
|
||||
|
||||
## [1.5.0-alpha.3](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.2...v1.5.0-alpha.3) (2021-08-03)
|
||||
|
||||
## [1.5.0-alpha.2](https://github.com/elyukai/optolith-client/compare/v1.5.0-alpha.1...v1.5.0-alpha.2) (2021-05-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* "Gaze into the Essence" spell is misspelled as "Gase into the Essence" ([42891f0](https://github.com/elyukai/optolith-client/commit/42891f070c35b74c49bb2c9e16152789cc8cc7df)), closes [#1180](https://github.com/elyukai/optolith-client/issues/1180)
|
||||
* "Physiostabilis" name typo ([e40f995](https://github.com/elyukai/optolith-client/commit/e40f995e341dc7fbcb1d9688c4b82a9187c180de)), closes [#1234](https://github.com/elyukai/optolith-client/issues/1234)
|
||||
* "Tradition (Druid)" has invalid prerequisite in data ([f8114c2](https://github.com/elyukai/optolith-client/commit/f8114c2f4318967631b71ea3614bf647e7826fa2)), closes [#1237](https://github.com/elyukai/optolith-client/issues/1237)
|
||||
* hook dagger is not marked as a parrying weapon ([ed74170](https://github.com/elyukai/optolith-client/commit/ed74170865b2caad8008a345db5c1705e0376bc5)), closes [#1232](https://github.com/elyukai/optolith-client/issues/1232)
|
||||
* multiple database fixes ([16bd668](https://github.com/elyukai/optolith-client/commit/16bd668313826ab8df0eeaeb5259a2f30304c134)), closes [#854](https://github.com/elyukai/optolith-client/issues/854) [#855](https://github.com/elyukai/optolith-client/issues/855) [#1136](https://github.com/elyukai/optolith-client/issues/1136) [#1213](https://github.com/elyukai/optolith-client/issues/1213) [#1238](https://github.com/elyukai/optolith-client/issues/1238) [#1242](https://github.com/elyukai/optolith-client/issues/1242)
|
||||
* special ability Deadly Thrust has SA Crossblock's rules text ([ec9bd71](https://github.com/elyukai/optolith-client/commit/ec9bd71e101abcaf61e84e3da340890e161896ea)), closes [#841](https://github.com/elyukai/optolith-client/issues/841)
|
||||
* weapon advantage of Kriegsbogen has wrong DP bonus ([8297455](https://github.com/elyukai/optolith-client/commit/8297455ddbd360035428a4ed244a684e6f1c7e7a)), closes [#1074](https://github.com/elyukai/optolith-client/issues/1074)
|
||||
* wrong English names for spell enhancements of Heptagramma ([a5df7c8](https://github.com/elyukai/optolith-client/commit/a5df7c863cded391609950fa5b8bf8474ffe2dfd)), closes [#848](https://github.com/elyukai/optolith-client/issues/848)
|
||||
* wrong negative prerequisite for Aspect Knowledge entry ([21146eb](https://github.com/elyukai/optolith-client/commit/21146eb2abdb16ff84f58d0223810603125ff63a)), closes [#1243](https://github.com/elyukai/optolith-client/issues/1243)
|
||||
* wrong talent value profession Spider Witch, closes [#1250](https://github.com/elyukai/optolith-client/issues/1250)
|
||||
|
||||
## [1.5.0-alpha.1](https://github.com/elyukai/optolith-client/compare/v1.4.3-alpha.1...v1.5.0-alpha.1) (2021-05-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 2H and i weapon settings missing on character sheet ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94)), closes [#797](https://github.com/elyukai/optolith-client/issues/797)
|
||||
* brawling weapons have no parry on character sheet ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8)), closes [#1229](https://github.com/elyukai/optolith-client/issues/1229)
|
||||
* cannot remove dependent entry with multiple activations ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76)), closes [#1097](https://github.com/elyukai/optolith-client/issues/1097)
|
||||
* enhancements on transferred unfamiliar spells as dependency ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414)), closes [#827](https://github.com/elyukai/optolith-client/issues/827)
|
||||
* equipment info crash ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* filter professions with CTs higher than EL allows ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02)), closes [#1244](https://github.com/elyukai/optolith-client/issues/1244)
|
||||
* include latest database changes ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7))
|
||||
* ini/mov modifiers on armor are handled as penalties ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb)), closes [#1091](https://github.com/elyukai/optolith-client/issues/1091)
|
||||
* language specializations always cost 1 AP and are multi-buyable ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf)), closes [#1082](https://github.com/elyukai/optolith-client/issues/1082)
|
||||
* large spell selection and restrictions for Animists ([2ad037e](https://github.com/elyukai/optolith-client/commit/2ad037ecbc2727f360161d6500f2aeaef3c4a67c)), closes [#803](https://github.com/elyukai/optolith-client/issues/803)
|
||||
* patch security vulnerabilities ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98))
|
||||
* portraits with transparency have black background ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220)), closes [#800](https://github.com/elyukai/optolith-client/issues/800)
|
||||
* previous database fixes ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6)), closes [#792](https://github.com/elyukai/optolith-client/issues/792) [#824](https://github.com/elyukai/optolith-client/issues/824) [#832](https://github.com/elyukai/optolith-client/issues/832) [#850](https://github.com/elyukai/optolith-client/issues/850) [#851](https://github.com/elyukai/optolith-client/issues/851) [#1139](https://github.com/elyukai/optolith-client/issues/1139) [#1224](https://github.com/elyukai/optolith-client/issues/1224)
|
||||
* primary attribute dropdown does not handle data correctly ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f)), closes [#798](https://github.com/elyukai/optolith-client/issues/798)
|
||||
* profession with due to EL impossible attribute values can be selected ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e)), closes [#1109](https://github.com/elyukai/optolith-client/issues/1109)
|
||||
* spellworks from professions do not register dependencies ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec)), closes [#1240](https://github.com/elyukai/optolith-client/issues/1240)
|
||||
* typo in effect of Bliksemblind spell (Dutch) ([c58279c](https://github.com/elyukai/optolith-client/commit/c58279cc95aba5506824510d83aa6ffbc410107a)), closes [#805](https://github.com/elyukai/optolith-client/issues/805)
|
||||
* weight of Elves is not generated correctly ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21)), closes [#813](https://github.com/elyukai/optolith-client/issues/813)
|
||||
|
||||
## [1.4.3-alpha.1](https://github.com/elyukai/optolith-client/compare/v1.4.3-alpha.0...v1.4.3-alpha.1) (2020-11-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **sheets:** removed margin of hit zone sheet ([e637136](https://github.com/elyukai/optolith-client/commit/e63713687eff9b825a79d159fb8b44dc26f64ffe)), closes [#788](https://github.com/elyukai/optolith-client/issues/788)
|
||||
* adding LE should affect CON ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a)), closes [#787](https://github.com/elyukai/optolith-client/issues/787)
|
||||
|
||||
## [1.4.2](https://github.com/elyukai/optolith-client/compare/v1.4.1...v1.4.2) (2020-11-08)
|
||||
|
||||
### Enhancements
|
||||
|
||||
+24
-18
@@ -28,7 +28,7 @@ You can suggest features either in the issues tracker or in one of the [listed f
|
||||
|
||||
### Important note
|
||||
|
||||
Due to licensing, the YAML files containing the crunch elements from the books are **not** included in this repository but in a separate private one. If you only plan to contribute something independent of the database, you can just get the database from an alpha installation. On Windows, for example, the database is available under `Resources/app.asar.unpacked/app/Data` inside the installation folder. If you plan to contribute anything to the database itself and thus need to access its source, please join my [Discord Server](https://discord.gg/uDyR4yr) and text me in a channel on the server or PN me (@elyukai).
|
||||
Due to licensing, the YAML files containing the crunch elements from the books are **not** included in this repository but in a separate private one. Please join my [Discord Server](https://discord.gg/uDyR4yr) if you want to contribute and thus need to access them. Just text me in a channel on the server or PN me (@elyukai).
|
||||
|
||||
### Basic development rules
|
||||
|
||||
@@ -40,12 +40,6 @@ Due to licensing, the YAML files containing the crunch elements from the books a
|
||||
- Code should follow functional programming practices while maintaining readablilty – which can be an issue using JavaScript/TypeScript, which is why in certain circumstances imperative/OOP code is allowed. It is also possible to write Reason/OCaml code, which compiles to JS.
|
||||
- The contribution has to be easy to understand not only for players using the app, but also for coders trying to further enhance the app: Please document *at least* module exports!
|
||||
|
||||
### Install required software
|
||||
|
||||
- **Git** — <https://git-scm.com/downloads> or via a package manager
|
||||
- **Node.js** with **npm** — <https://nodejs.org/en/> or via [`nvm`](https://github.com/nvm-sh/nvm)
|
||||
- *Optional:* **Visual Studio Code** — <https://code.visualstudio.com/> (you can use your own editor as well, there are project-specific VSCode settings within this repository, though)
|
||||
|
||||
### Prepare the repo
|
||||
|
||||
Clone the repo, including the private submodule.
|
||||
@@ -54,7 +48,7 @@ Clone the repo, including the private submodule.
|
||||
git clone --recurse-submodules https://github.com/elyukai/optolith-client.git
|
||||
```
|
||||
|
||||
Make sure Node.js (lastest version) is installed, nagivate into the created project folder and install all dependencies using the following command:
|
||||
Make sure Node.js (lastest version) is installed and run
|
||||
|
||||
```sh
|
||||
npm i
|
||||
@@ -64,17 +58,27 @@ This installs all necessary packages.
|
||||
|
||||
### First run
|
||||
|
||||
Compile the source code. `js:` is for TypeScript/Webpack. Webpack does not check the TypeScript validity, to check this, use `ts:` commands.
|
||||
Compile the source code. `js:` is for TypeScript/Webpack, `re:` for Reason/OCaml and `css:` for CSS/Sass. Webpack does not check the TypeScript validity, to check this, use `ts:` commands.
|
||||
|
||||
Build the source by running
|
||||
You have to run the Reason build first as it generates TypeScript files...
|
||||
|
||||
```sh
|
||||
npm run re:build
|
||||
npm run css:build
|
||||
npm run js:build
|
||||
```
|
||||
|
||||
(There's also a shorthand for that:)
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
|
||||
…or watch it for better performance for subsequent compiling on save (you need to use separate terminal instances then).
|
||||
|
||||
```sh
|
||||
npm run re:watch
|
||||
npm run css:watch
|
||||
npm run js:watch
|
||||
```
|
||||
|
||||
@@ -91,20 +95,22 @@ Run the app:
|
||||
npm start
|
||||
```
|
||||
|
||||
The [debug](https://www.npmjs.com/package/debug) package is used for some general logging. If you want to see its output, you need to set the `DEBUG` environment variable.
|
||||
### Stylesheets
|
||||
|
||||
To compile the SCSS files, use
|
||||
|
||||
```sh
|
||||
DEBUG=* npm start
|
||||
```
|
||||
|
||||
In PowerShell you need to write this instead:
|
||||
|
||||
```sh
|
||||
$env:DEBUG='*'; npm start
|
||||
npm run css:build
|
||||
```
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
Clean the files built by the Reason compiler, if there are any issue with it.
|
||||
|
||||
```sh
|
||||
npm run re:clean
|
||||
```
|
||||
|
||||
Lint all project TS files using ESLint.
|
||||
|
||||
```sh
|
||||
|
||||
@@ -2,21 +2,12 @@
|
||||
|
||||
Welcome to the repository for Optolith, a desktop application for The Dark Eye 5th Edition. Nice to see you here! 👋🏻
|
||||
|
||||
> [!CAUTION]
|
||||
> This is the branch of the upcoming V2 version. This is **not production-ready**. If you need access to the V1 line of releases, check out the `develop-V1` branch.
|
||||
>
|
||||
> Note that the self-building constraints below also apply to the V1.
|
||||
|
||||
## Install
|
||||
|
||||
You can download the app [here (German page)](http://www.ulisses-ebooks.de/product/209711) or here [here (English page)](http://www.drivethrurpg.com/product/220253) (same app on both pages). You'll get an installer for Windows with Auto Update support and a DMG for Mac. For Linux, you get an AppImage, which _should_ support Auto Update but it doesnt seem to work currently. You also get a tar.gz if you don't like AppImage.
|
||||
You can download the app [here (German page)](http://www.ulisses-ebooks.de/product/209711) or here [here (English page)](http://www.drivethrurpg.com/product/220253) (same app on both pages). You'll get an installer for Windows with Auto Update support and a DMG for Mac. For Linux, you get an AppImage, which *should* support Auto Update but it doesnt seem to work currently. You also get a tar.gz if you don't like AppImage.
|
||||
|
||||
It is available for Windows 7+ as well as for Linux (Ubuntu 12.04+, Fedora 21, Debian 8) and macOS 10.9+ x64.
|
||||
|
||||
### Self-Building and Package Registries
|
||||
|
||||
Please note that self-building is only possible in a restricted way, as the underlying database is private due to licensing reasons. You can follow the in the [Contributing Guide](CONTRIBUTING.md) to obtain a version of the database from an already existing release. Please note that due to these licensing restrictions distribution via package managers like `brew` or `apt`
|
||||
|
||||
## Goal
|
||||
|
||||
Optolith was built and is developed based on a few key criteria:
|
||||
|
||||
-1
Submodule app/Database deleted from d17be0b48b
@@ -1,129 +0,0 @@
|
||||
## 1.5.1
|
||||
|
||||
### Miglioramenti
|
||||
|
||||
* **MapTool:** aggiunto collegamento all’estensione accanto al pulsante di esportazione ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Correzioni
|
||||
|
||||
* **MapTool:** non era possibile esportare senza avatar ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), corregge [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** modificato il formato di esportazione alla versione 1.11.4 di MapTool ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* **MapTool:** aggiunte traduzioni italiane per l’esportazione ([3f87dca](https://github.com/elyukai/optolith-client/commit/3f87dca910ce5229c7fd5378ba5f4177cbf431ac)), corregge [#1350](https://github.com/elyukai/optolith-client/issues/1350)
|
||||
* Il vantaggio *Affabilità* poteva essere acquistato con lo svantaggio *Incompetenza* su una competenza sociale ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), corregge [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Prerequisiti barrati venivano renderizzati con delle tilde prima e dopo ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), corregge [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Features
|
||||
|
||||
* Esportazione per MapTool ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Traduzione portoghese ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Supporto nativo per Apple M1 ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Migliorie
|
||||
|
||||
* Sicurezza ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) e altre)
|
||||
|
||||
### Correzioni
|
||||
|
||||
* PV aggiuntivi influenzano il valore minimo di COS e non FOR ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Aggiunti gli indicatori per armi a due mani (2M) e improvvisate (i) sul Foglio d’identità ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Armi usate con Armi nude non avevano il valore di Parata indicato sul Foglio d’identità ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* Un valore che dipende da una singola attivazione di un altro valore impedisce di rimuovere tutte le attivazioni di quel valore, se ce ne sono più attive ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Valori che hanno effetto sulle magie estranee potevano essere rimossi se venivano acquistati miglioramenti di incantesimo ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* Terminazione anomala dovuto alle informazioni sull’equipaggiamento ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Le professioni che garantiscono un Valore di Tecnica di Combattimento più elevato del massimo concesso dal Livello d’Esperienza non venivano filtrate ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* I modificatori di Ini e Mov delle armature venivano gestiti come penalità ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Specializzazioni di linguaggio non costavano sempre 1 PAvv e non potevano essere scelte più volte per una singola lingua ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Immagini con trasparenza venivano mostrate con sfondo nero ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* Il pacchetto culturale bornese modificava Cucinare invece di Lavorazione del cuoio ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* Alcuni errori di battitura in oggetti, lingue e scritture ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#832](https://github.com/elyukai/optolith-client/issues/832))
|
||||
* Corretto il titolo di Volontà del ferro ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#850](https://github.com/elyukai/optolith-client/issues/850))
|
||||
* Incantesimi e liturgie venivano indicate con punteggiatura e maiuscole diverse ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#851](https://github.com/elyukai/optolith-client/issues/851))
|
||||
* Le modifiche di attributo primario per gli oggetti venivano mostrate soltanto nella finestra di modifica, non sul Foglio d’identità ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Le professioni con valori di attributi impossibili (a causa del Livello d’Esperienza) non possono più essere selezionate ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Magie di evocazione fornite dalla professione non registravano i prerequisiti corretti ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* Il peso degli elfi non veniva calcolato in maniera corretta ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* Gli attributi al valore minimo (8) non permettevano di raggiungere un Valore di Tecnica di Combattimento superiore al 6 se facevano da Attributo primario per la Tecnica
|
||||
([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* PDF troppo grande quando si utilizza lo sfondo ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* Sistema di aggiornamento non funziona con AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* Alcuni requisiti sugli Attributi primari non venivano rispettati ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Gli indicatori di Raggio influenzavano altri campi ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* Si richiedeva soltanto una lingua compatibile invece di tutte ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Le scritture non richiedevano la conoscenza di una lingua compatibile ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Alcune proprietà degli oggetti non venivano salvati ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* La selezione dei propri incantesimi estranei era opzionale ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* Le Tecniche di Combattimento non influenzavano il minimo richiesto sugli Attributi primari ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* Salvando un eroe veniva cancellata la cronologia per più eroi ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* Allargate le righe della seconda colonna di abilità nel Foglio d’identità ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** a tutti coloro che forniscono commenti, suggerimenti e aiuto sui vari canali sociali e un grazie alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Elidia (@FloraMayr)](https://github.com/FloraMayr)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lector (@Lector)](https://github.com/Lector)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [Marc Schwering (@m4rcsch)](https://github.com/m4rcsch)
|
||||
- [Pepelios (@Pepelios)](https://github.com/Pepelios)
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Migliorie
|
||||
|
||||
- Ridotto il tempo di avvio. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Correzioni
|
||||
|
||||
- Le formule della caratteristiche derivate occupano troppo spazio sul foglio d’identità. [#729](https://github.com/elyukai/optolith-client/issues/729)
|
||||
- Rimozione dei requisiti doppi in alcune abilità speciali. [#730](https://github.com/elyukai/optolith-client/issues/730)
|
||||
- Correzioni minori della localizzazione italiana. [#731](https://github.com/elyukai/optolith-client/issues/731)
|
||||
- Alcune tecniche di combattimento possono essere impostate a valori più elevati di quanto ammesso dal regolamento. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- Il margine superiore delle finestre in sovrimpressione, come nell’inserimento dei vantaggi, è troppo stretto. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- Il bonus di impatto non viene calcolato per alcune armi. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- La professione *Mago bianco (Accademia Garetiana di Spada e Bastone)* dal **Regolamento** ha un valore di PAvv sbagliato e *Astronomia 4* invece di *Alchimia 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Campo di testo delle abilità degli animali nel foglio d’identità è troppo piccolo per contenere tutte le abilità. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- L’abilità speciale *Bladestorm* si applica alle tecniche di combattimento sbagliate. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Immagini con estensione in maiuscoletto non vengono accettate come ritratto. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- Viene mostrata l’icona di caricamento invece del ritratto su fogli d’identità di personaggi senza ritratto. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Su Mac, con lo sfondo pergamenato attivato, vengono generate pagine bianche di troppo nel PDF finale. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** a tutti coloro che forniscono commenti, suggerimenti e aiuto sui vari canali sociali e un grazie alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Correzioni
|
||||
|
||||
- La localizzazione italiana non era disponibile.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
Primo rilascio della versione italiana.
|
||||
|
||||
### API
|
||||
|
||||
Ci sono importanti modifiche in questa nuova versione, che cambiano anche il modo in cui sono memorizzati i dati. Se fai uso dei file memorizzati da Optolith, è possibile fare un salto su [Discord](https://discord.gg/wfdgB9g) in modo da avere più informazioni sulle modifiche nel dettaglio.
|
||||
Siccome sempre più persone fanno uso dei dati di Optolith, da questa versione in poi seguiremo le regole del *versionamento semantico*, in modo da permettere agli utenti di fare affidamento alla compatibilità dei file sorgente e dei file dei dati, verificandone soltanto il numero di versione.
|
||||
Ci sarà un nuovo formato di dati sia per gli eroi memorizzati che per i dati sorgente.
|
||||
Le modifiche non sono ancora finalizzate e la nuova versione maggiore è ancora lontana, ma è consigliato aggiornarsi al nuovo formato di dati il prima possibile.
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
@@ -1,16 +0,0 @@
|
||||
## 1.5.1
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **MapTool:** add links to extension next to export button ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MapTool:** without an avatar no export was possible ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), closes [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** changed export format for MapTool version 1.11.4 ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* Advantage *Inspire Confidence* could be bought with an active *Incompetent* in a social skill ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), closes [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Strikethrough prerequisites were rendered with surrounding tildes instead ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), closes [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
Lançamento inicial.
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "optolith-client",
|
||||
"version": "0.1.0",
|
||||
"sources": [
|
||||
{ "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",
|
||||
"in-source": true
|
||||
},
|
||||
"suffix": ".bs.js",
|
||||
"bs-dependencies": [
|
||||
"@glennsl/bs-jest",
|
||||
"@rescript/react",
|
||||
"bs-decoders",
|
||||
"reductive"
|
||||
],
|
||||
"reason": {
|
||||
"react-jsx": 3
|
||||
},
|
||||
"warnings": {
|
||||
"number": "+A-42-48",
|
||||
"error": "+A-3-4-27-32-41-44-102"
|
||||
},
|
||||
"namespace": true
|
||||
}
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
// @ts-check
|
||||
import builder from "electron-builder"
|
||||
import { notarize } from "./notarize.js"
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
// Detect channel
|
||||
|
||||
if (!args.includes("--stable") && !args.includes("--prerelease")) {
|
||||
throw new TypeError(`Missing channel argument (either "--stable" or "--prerelease")`)
|
||||
}
|
||||
|
||||
const isPrerelease = args.includes("--prerelease")
|
||||
|
||||
console.log(`Detected channel: ${isPrerelease ? "prerelease" : "stable"}`)
|
||||
|
||||
// Detect OS
|
||||
|
||||
let osKey
|
||||
let osName
|
||||
|
||||
if (process.argv.includes("--linux") || process.platform === "linux") {
|
||||
osKey = /** @type {const} */ ("LINUX")
|
||||
osName = "Linux"
|
||||
} else if (process.argv.includes("--mac") || process.platform === "darwin") {
|
||||
osKey = /** @type {const} */ ("MAC")
|
||||
osName = "macOS"
|
||||
} else if (process.argv.includes("--win") || process.platform === "win32") {
|
||||
osKey = /** @type {const} */ ("WINDOWS")
|
||||
osName = "Windows"
|
||||
} else {
|
||||
throw new TypeError(`The target operating system cannot be inferred from the environment.`)
|
||||
}
|
||||
|
||||
console.log(`Detected operating system: ${osName}`)
|
||||
|
||||
// Prepare electron-builder configuration
|
||||
|
||||
/**
|
||||
* @type {import("electron-builder").Configuration}
|
||||
*/
|
||||
const config = {
|
||||
appId: isPrerelease ? "com.lukasobermann.optolithinsider" : "com.lukasobermann.optolith",
|
||||
productName: isPrerelease ? "Optolith Insider" : "Optolith",
|
||||
copyright:
|
||||
"© 2017–present Lukas Obermann. This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
|
||||
files: [
|
||||
".webpack/**",
|
||||
"LICENSE",
|
||||
"node_modules/optolith-database-schema/schema/**",
|
||||
"src/database/contents/cache/**",
|
||||
"src/database/contents/Compatibility/**",
|
||||
"src/database/contents/Data/**",
|
||||
],
|
||||
asar: false, // otherwise optolith-database-schema package is not properly resolved
|
||||
asarUnpack: [
|
||||
"node_modules/optolith-character-schema/**",
|
||||
"node_modules/optolith-database-schema/**",
|
||||
"src/database/contents/**",
|
||||
],
|
||||
directories: {
|
||||
output: isPrerelease ? "dist/insider" : "dist",
|
||||
},
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: "nsis",
|
||||
arch: ["x64", "ia32"],
|
||||
},
|
||||
],
|
||||
icon: isPrerelease ? "src/assets/icon/AppList.targetsize-512.pre.png" : "src/assets/icon/icon.ico",
|
||||
artifactName: isPrerelease ? "OptolithInsiderSetup_${version}.${ext}" : "OptolithSetup_${version}.${ext}",
|
||||
},
|
||||
nsis: {
|
||||
oneClick: false,
|
||||
perMachine: false,
|
||||
allowToChangeInstallationDirectory: true,
|
||||
differentialPackage: true,
|
||||
},
|
||||
linux: {
|
||||
category: "RolePlaying",
|
||||
target: [
|
||||
{
|
||||
target: "AppImage",
|
||||
arch: ["x64"],
|
||||
},
|
||||
{
|
||||
target: "tar.gz",
|
||||
arch: ["x64"],
|
||||
},
|
||||
],
|
||||
executableName: isPrerelease ? "OptolithInsider" : "Optolith",
|
||||
icon: isPrerelease ? "src/assets/icon/icon.pre.png" : "src/assets/icon/AppList.targetsize-512.png",
|
||||
artifactName: isPrerelease ? "OptolithInsider_${version}.${ext}" : "Optolith_${version}.${ext}",
|
||||
},
|
||||
mac: {
|
||||
category: "public.app-category.role-playing-games",
|
||||
type: "distribution",
|
||||
target: [
|
||||
{
|
||||
target: "default",
|
||||
arch: "universal",
|
||||
},
|
||||
],
|
||||
icon: isPrerelease ? "src/assets/icon/AppIcon.pre.icns" : "src/assets/icon/AppIcon.icns",
|
||||
artifactName: isPrerelease ? "OptolithInsider_${version}.${ext}" : "Optolith_${version}.${ext}",
|
||||
mergeASARs: false,
|
||||
darkModeSupport: true,
|
||||
gatekeeperAssess: true,
|
||||
},
|
||||
publish: {
|
||||
provider: "generic",
|
||||
url: isPrerelease ? `${process.env.UPDATE_URL}/insider/\${os}` : `${process.env.UPDATE_URL}/\${os}`,
|
||||
channel: "latest",
|
||||
},
|
||||
afterSign: osKey === "MAC" ? notarize : undefined,
|
||||
}
|
||||
|
||||
await builder.build({
|
||||
config,
|
||||
targets: builder.Platform[osKey].createTarget(),
|
||||
})
|
||||
|
||||
console.log(`Build finished successfully.`)
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,58 +0,0 @@
|
||||
// @ts-check
|
||||
import { notarize as electronNotarize } from "@electron/notarize"
|
||||
|
||||
/**
|
||||
* Notarizes the app package after it has been built. It detects the
|
||||
* authentication method from the environment variables.
|
||||
* @param context {import("electron-builder").AfterPackContext}
|
||||
*/
|
||||
export const notarize = async context => {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
|
||||
if (electronPlatformName === "darwin") {
|
||||
const appName = context.packager.appInfo.productFilename
|
||||
|
||||
if (
|
||||
typeof process.env.APPLEID === "string" &&
|
||||
typeof process.env.APPLEIDPASS === "string" &&
|
||||
typeof process.env.TEAMID === "string"
|
||||
) {
|
||||
console.log(`Notarizing "${appName}.app" via Apple ID ...`)
|
||||
await electronNotarize({
|
||||
tool: "notarytool",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS,
|
||||
teamId: process.env.TEAMID,
|
||||
})
|
||||
console.log(`Notarization successful`)
|
||||
} else if (
|
||||
typeof process.env.APPLEAPIKEY === "string" &&
|
||||
typeof process.env.APPLEAPIKEYID === "string" &&
|
||||
typeof process.env.APPLEAPIISSUER === "string"
|
||||
) {
|
||||
console.log(`Notarizing "${appName}.app" via App Store Connect API ...`)
|
||||
await electronNotarize({
|
||||
tool: "notarytool",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleApiKey: process.env.APPLEAPIKEY,
|
||||
appleApiKeyId: process.env.APPLEAPIKEYID,
|
||||
appleApiIssuer: process.env.APPLEAPIISSUER,
|
||||
})
|
||||
console.log(`Notarization successful`)
|
||||
} else if (typeof process.env.KEYCHAINPROFILE === "string") {
|
||||
console.log(`Notarizing "${appName}.app" via Keychain ...`)
|
||||
await electronNotarize({
|
||||
tool: "notarytool",
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
keychain: process.env.KEYCHAIN,
|
||||
keychainProfile: process.env.KEYCHAINPROFILE,
|
||||
})
|
||||
console.log(`Notarization successful`)
|
||||
} else {
|
||||
throw new Error(
|
||||
`Notarization failed: No valid authentication method found in environment. Please provide either APPLEID and APPLEIDPASS, or APPLEAPIKEY, APPLEAPIKEYID and APPLEAPIISSUER, or (optional) KEYCHAIN and KEYCHAINPROFILE.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Generated by ReScript, PLEASE EDIT WITH CARE
|
||||
'use strict';
|
||||
|
||||
|
||||
var WINDOWS = {};
|
||||
|
||||
var LINUX = {};
|
||||
|
||||
var MAC = {};
|
||||
|
||||
var Platform = {
|
||||
WINDOWS: WINDOWS,
|
||||
LINUX: LINUX,
|
||||
MAC: MAC
|
||||
};
|
||||
|
||||
var Config = {};
|
||||
|
||||
var ElectronBuilder = {
|
||||
Platform: Platform,
|
||||
Config: Config
|
||||
};
|
||||
|
||||
exports.ElectronBuilder = ElectronBuilder;
|
||||
/* No side effect */
|
||||
@@ -0,0 +1,361 @@
|
||||
module ElectronBuilder = {
|
||||
type target;
|
||||
|
||||
module Platform = {
|
||||
module WINDOWS = {
|
||||
[@bs.module "electron-builder"] [@bs.scope ("Platform", "WINDOWS")]
|
||||
external createTarget: unit => target = "createTarget";
|
||||
};
|
||||
|
||||
module LINUX = {
|
||||
[@bs.module "electron-builder"] [@bs.scope ("Platform", "LINUX")]
|
||||
external createTarget: unit => target = "createTarget";
|
||||
};
|
||||
|
||||
module MAC = {
|
||||
[@bs.module "electron-builder"] [@bs.scope ("Platform", "MAC")]
|
||||
external createTarget: unit => target = "createTarget";
|
||||
};
|
||||
};
|
||||
|
||||
module Config = {
|
||||
type directories = {
|
||||
buildResources: option(string),
|
||||
output: option(string),
|
||||
app: option(string),
|
||||
};
|
||||
|
||||
type target = {
|
||||
target: string,
|
||||
arch: option(array(string)),
|
||||
};
|
||||
|
||||
type mac = {
|
||||
artifactName: option(string),
|
||||
category: option(string),
|
||||
target: option(string),
|
||||
icon: option(string),
|
||||
[@bs.as "type"]
|
||||
type_: option(string),
|
||||
hardenedRuntime: option(bool),
|
||||
gatekeeperAssess: option(bool),
|
||||
};
|
||||
|
||||
type dmg = {sign: option(bool)};
|
||||
|
||||
type win = {
|
||||
artifactName: option(string),
|
||||
target: option(array(target)),
|
||||
icon: option(string),
|
||||
};
|
||||
|
||||
type nsis = {
|
||||
perMachine: option(bool),
|
||||
deleteAppDataOnUninstall: option(bool),
|
||||
};
|
||||
|
||||
type linux = {
|
||||
artifactName: option(string),
|
||||
category: option(string),
|
||||
target: option(array(target)),
|
||||
executableName: option(string),
|
||||
icon: option(string),
|
||||
};
|
||||
|
||||
type publish = {
|
||||
provider: string,
|
||||
url: string,
|
||||
channel: option(string),
|
||||
useMultipleRangeRequest: option(bool),
|
||||
};
|
||||
|
||||
type t = {
|
||||
appId: option(string),
|
||||
productName: option(string),
|
||||
copyright: option(string),
|
||||
directories: option(directories),
|
||||
mac: option(mac),
|
||||
dmg: option(dmg),
|
||||
win: option(win),
|
||||
nsis: option(nsis),
|
||||
linux: option(linux),
|
||||
files: option(array(string)),
|
||||
publish: option(publish),
|
||||
};
|
||||
};
|
||||
|
||||
type options = {
|
||||
config: Config.t,
|
||||
targets: target,
|
||||
};
|
||||
|
||||
[@bs.module "electron-builder"]
|
||||
external build: options => Js.Promise.t(list(string)) = "build";
|
||||
};
|
||||
|
||||
// module Publisher = {
|
||||
// // require("dotenv").config()
|
||||
// // const ftp = require ("basic-ftp")
|
||||
// // const Client = require ("ssh2-sftp-client")
|
||||
// // const fs = require ("fs")
|
||||
// // const path = require ("path")
|
||||
// // const semver = require ("semver");
|
||||
|
||||
// type channel =
|
||||
// | Stable
|
||||
// | Insider;
|
||||
|
||||
// type os =
|
||||
// | Windows
|
||||
// | OSX
|
||||
// | Linux;
|
||||
|
||||
// /**
|
||||
// * Needed env variables:
|
||||
// * - `HOST`
|
||||
// * - `USERNAME`
|
||||
// * - `PASSWORD`
|
||||
// */
|
||||
// let publishToServer = (channel, os) => {
|
||||
// let subFolder =
|
||||
// switch (os) {
|
||||
// | Windows => "win"
|
||||
// | Linux => "linux"
|
||||
// | OSX => "mac"
|
||||
// };
|
||||
|
||||
// let updateYmlName =
|
||||
// switch (os) {
|
||||
// | Windows => "latest.yml"
|
||||
// | Linux => "latest-linux.yml"
|
||||
// | OSX => "latest-mac.yml"
|
||||
// };
|
||||
|
||||
// let distPath =
|
||||
// switch (channel) {
|
||||
// | Insider => ["dist", "insider"]
|
||||
// | Stable => ["dist"]
|
||||
// };
|
||||
|
||||
// let subPathServer =
|
||||
// switch (channel) {
|
||||
// | Insider => "insider/"
|
||||
// | Stable => ""
|
||||
// };
|
||||
|
||||
// let serverPath = "./update.optolith.app/" ++ subPathServer ++ subFolder;
|
||||
|
||||
// let regex =
|
||||
// switch (channel) {
|
||||
// | Insider =>
|
||||
// switch (os) {
|
||||
// | Windows =>
|
||||
// %re
|
||||
// "/^OptolithInsiderSetup_(.+)\\.exe$/"
|
||||
// | Linux =>
|
||||
// %re
|
||||
// "/^OptolithInsider_(.+)\\.(?:AppImage|tar\\.gz)$/"
|
||||
// | OSX =>
|
||||
// %re
|
||||
// "/^OptolithInsider_(.+)\\.(?:zip|dmg)$/"
|
||||
// }
|
||||
// | Stable =>
|
||||
// switch (os) {
|
||||
// | Windows =>
|
||||
// %re
|
||||
// "/^OptolithSetup_(.+)\\.exe$/"
|
||||
// | Linux =>
|
||||
// %re
|
||||
// "/^Optolith_(.+)\\.(?:AppImage|tar\\.gz)$/"
|
||||
// | OSX =>
|
||||
// %re
|
||||
// "/^Optolith_(.+)\\.(?:zip|dmg)$/"
|
||||
// }
|
||||
// };
|
||||
|
||||
// let recreateFileNames =
|
||||
// switch (channel) {
|
||||
// | Insider =>
|
||||
// switch (os) {
|
||||
// | Windows => (v => ["OptolithInsiderSetup_" ++ v ++ ".exe"])
|
||||
// | Linux => (
|
||||
// v => [
|
||||
// "OptolithInsider_" ++ v ++ ".AppImage",
|
||||
// "OptolithInsider_" ++ v ++ ".tar.gz",
|
||||
// ]
|
||||
// )
|
||||
// | OSX => (
|
||||
// v => [
|
||||
// "OptolithInsider_" ++ v ++ ".dmg",
|
||||
// "OptolithInsider_" ++ v ++ ".zip",
|
||||
// ]
|
||||
// )
|
||||
// }
|
||||
// | Stable =>
|
||||
// switch (os) {
|
||||
// | Windows => (v => ["OptolithSetup_" ++ v ++ ".exe"])
|
||||
// | Linux => (
|
||||
// v => [
|
||||
// "Optolith_" ++ v ++ ".AppImage",
|
||||
// "Optolith_" ++ v ++ ".tar.gz",
|
||||
// ]
|
||||
// )
|
||||
// | OSX => (
|
||||
// v => ["Optolith_" ++ v ++ ".dmg", "Optolith_" ++ v ++ ".zip"]
|
||||
// )
|
||||
// }
|
||||
// };
|
||||
// ();
|
||||
// // const allFiles = await fs.promises.readdir (path.join (...distPath))
|
||||
// // const allInstallerNames = allFiles .filter (fileName => regex .test (fileName))
|
||||
// // const allInstallerVersionsSorted =
|
||||
// // allInstallerNames .map (fileName => regex .exec (fileName) [1])
|
||||
// // .sort (semver .rcompare)
|
||||
// // if (allInstallerVersionsSorted .length < 1) {
|
||||
// // throw new RangeError ("Array of sorted installer versions is empty")
|
||||
// // }
|
||||
// // const latestFileNames = recreateFileNames (allInstallerVersionsSorted [0])
|
||||
// // console.log(`Files to upload: ${[updateYmlName, ...latestFileNames] .join (", ")}`);
|
||||
// // // Actual server connection
|
||||
// // const client = new Client ()
|
||||
// // console.log("Connecting to server...");
|
||||
// // await client.connect ({
|
||||
// // host: process.env.HOST,
|
||||
// // port: 22,
|
||||
// // username: process.env.USERNAME,
|
||||
// // password: process.env.PASSWORD
|
||||
// // })
|
||||
// // console.log(`Server connection established`);
|
||||
// // for (const fileName of latestFileNames) {
|
||||
// // console.log(`Uploading ${fileName}`);
|
||||
// // const stream = fs.readFileSync (path.join (...distPath, fileName))
|
||||
// // const fileRes = await client.put (stream, `${serverPath}/${fileName}`)
|
||||
// // console.log(`Upload done: ${fileName} (${fileRes})`);
|
||||
// // }
|
||||
// // console.log(`Uploading ${updateYmlName}`);
|
||||
// // const updateYml = fs.readFileSync (path.join (...distPath, updateYmlName))
|
||||
// // const ymlRes = await client.put (updateYml, `${serverPath}/${updateYmlName}`)
|
||||
// // console.log(`Upload done: ${updateYmlName} (${ymlRes})`);
|
||||
// // await client.end ()
|
||||
// // console.log("Closed server connection.");
|
||||
// };
|
||||
// };
|
||||
|
||||
// module Stable = {
|
||||
// let config: ElectronBuilder.Config.t = {
|
||||
// appId: Some("com.lukasobermann.optolith"),
|
||||
// productName: Some("Optolith"),
|
||||
// copyright:
|
||||
// Some(
|
||||
// "This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
|
||||
// ),
|
||||
// directories:
|
||||
// Some({buildResources: None, output: Some("dist"), app: None}),
|
||||
// files: Some([|"app/**/*"|]),
|
||||
// mac:
|
||||
// Some({
|
||||
// artifactName: Some("Optolith_${version}.${ext}"),
|
||||
// category: Some("public.app-category.role-playing-games"),
|
||||
// target: Some("default"),
|
||||
// icon: Some("app/icon.icns"),
|
||||
// type_: Some("distribution"),
|
||||
// hardenedRuntime: Some(true),
|
||||
// gatekeeperAssess: Some(false),
|
||||
// }),
|
||||
// dmg: Some({sign: Some(false)}),
|
||||
// win:
|
||||
// Some({
|
||||
// artifactName: Some("OptolithSetup_${version}.${ext}"),
|
||||
// target: Some([|{target: "nsis", arch: Some([|"x64", "ia32"|])}|]),
|
||||
// icon: Some("app/icon.ico"),
|
||||
// }),
|
||||
// nsis:
|
||||
// Some({perMachine: Some(true), deleteAppDataOnUninstall: Some(false)}),
|
||||
// linux:
|
||||
// Some({
|
||||
// artifactName: Some("Optolith_${version}.${ext}"),
|
||||
// category: Some("RolePlaying"),
|
||||
// target:
|
||||
// Some([|
|
||||
// {target: "AppImage", arch: Some([|"x64"|])},
|
||||
// {target: "tar.gz", arch: Some([|"x64"|])},
|
||||
// |]),
|
||||
// executableName: Some("Optolith"),
|
||||
// icon: Some("app"),
|
||||
// }),
|
||||
// publish:
|
||||
// Some({
|
||||
// provider: "generic",
|
||||
// url:
|
||||
// Belt.Option.getWithDefault(
|
||||
// Js.Dict.get(Node.Process.process##env, "PUBLISH_URL"),
|
||||
// "",
|
||||
// ),
|
||||
// channel: Some("latest"),
|
||||
// useMultipleRangeRequest: None,
|
||||
// }),
|
||||
// };
|
||||
|
||||
// let buildWindows = (): Js.Promise.t(unit) => {
|
||||
// Js.log("Building Optolith for Windows...");
|
||||
|
||||
// ElectronBuilder.build({
|
||||
// config,
|
||||
// targets: ElectronBuilder.Platform.WINDOWS.createTarget(),
|
||||
// })
|
||||
// |> Js.Promise.then_(x => {
|
||||
// Js.log("Optolith Build for Windows successful.");
|
||||
// x |> ignore |> Js.Promise.resolve;
|
||||
// })
|
||||
// |> Js.Promise.then_(_ =>
|
||||
// Js.Promise.resolve(Publisher.publishToServer(Stable, Windows))
|
||||
// )
|
||||
// |> Js.Promise.then_(_ => {
|
||||
// Js.log("Optolith Build for Windows deployed.");
|
||||
|
||||
// Js.Promise.resolve();
|
||||
// });
|
||||
// };
|
||||
|
||||
// let buildLinux = (): Js.Promise.t(unit) => {
|
||||
// Js.log("Building Optolith for Linux...");
|
||||
|
||||
// ElectronBuilder.build({
|
||||
// config,
|
||||
// targets: ElectronBuilder.Platform.LINUX.createTarget(),
|
||||
// })
|
||||
// |> Js.Promise.then_(x => {
|
||||
// Js.log("Optolith Build for Linux successful.");
|
||||
// x |> ignore |> Js.Promise.resolve;
|
||||
// })
|
||||
// |> Js.Promise.then_(_ =>
|
||||
// Js.Promise.resolve(Publisher.publishToServer(Stable, Linux))
|
||||
// )
|
||||
// |> Js.Promise.then_(_ => {
|
||||
// Js.log("Optolith Build for Linux deployed.");
|
||||
|
||||
// Js.Promise.resolve();
|
||||
// });
|
||||
// };
|
||||
|
||||
// let buildMac = (): Js.Promise.t(unit) => {
|
||||
// Js.log("Building Optolith for OSX...");
|
||||
|
||||
// ElectronBuilder.build({
|
||||
// config,
|
||||
// targets: ElectronBuilder.Platform.MAC.createTarget(),
|
||||
// })
|
||||
// |> Js.Promise.then_(x => {
|
||||
// Js.log("Optolith Build for OSX successful.");
|
||||
// x |> ignore |> Js.Promise.resolve;
|
||||
// })
|
||||
// |> Js.Promise.then_(_ =>
|
||||
// Js.Promise.resolve(Publisher.publishToServer(Stable, OSX))
|
||||
// )
|
||||
// |> Js.Promise.then_(_ => {
|
||||
// Js.log("Optolith Build for OSX deployed.");
|
||||
|
||||
// Js.Promise.resolve();
|
||||
// });
|
||||
// };
|
||||
// };
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @type {import ("electron-builder") .Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
appId: "com.lukasobermann.optolith",
|
||||
productName: "Optolith",
|
||||
copyright: "This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
|
||||
directories: {
|
||||
output: "dist"
|
||||
},
|
||||
files: [
|
||||
"app/**/*",
|
||||
"LICENSE"
|
||||
],
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: "nsis",
|
||||
arch: [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
icon: "app/icon.ico",
|
||||
artifactName: "OptolithSetup_${version}.${ext}"
|
||||
},
|
||||
nsis: {
|
||||
perMachine: true,
|
||||
differentialPackage: true,
|
||||
deleteAppDataOnUninstall: false,
|
||||
},
|
||||
linux: {
|
||||
category: "RolePlaying",
|
||||
executableName: "Optolith",
|
||||
icon: "app",
|
||||
target: [
|
||||
{
|
||||
target: "AppImage",
|
||||
arch: [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
target: "tar.gz",
|
||||
arch: [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
artifactName: "Optolith_${version}.${ext}"
|
||||
},
|
||||
mac: {
|
||||
category: "public.app-category.role-playing-games",
|
||||
type: "distribution",
|
||||
icon: "app/icon.icns",
|
||||
target: "default",
|
||||
artifactName: "Optolith_${version}.${ext}",
|
||||
hardenedRuntime: true,
|
||||
// entitlements: "deploy/entitlements.mac.plist",
|
||||
// entitlementsInherit: "deploy/entitlements.mac.plist",
|
||||
gatekeeperAssess: false,
|
||||
},
|
||||
dmg: {
|
||||
sign: false,
|
||||
},
|
||||
publish: {
|
||||
"provider": "generic",
|
||||
"url": `${process.env.UPDATE_URL}/\${os}`,
|
||||
"channel": "latest"
|
||||
},
|
||||
// afterSign: notarize,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// @ts-check
|
||||
const builder = require("electron-builder")
|
||||
const { getSystem } = require("./platform")
|
||||
|
||||
const [channel] = process.argv.slice(2)
|
||||
|
||||
if (channel !== "stable" && channel !== "prerelease") {
|
||||
throw new TypeError (`publishToServer requires a specified channel ("stable" or "prerelease"), but it received ${channel}`)
|
||||
}
|
||||
|
||||
const config =
|
||||
channel === "prerelease"
|
||||
? require("./build.prerelease.config.js")
|
||||
: require("./build.config.js")
|
||||
|
||||
process.on ('unhandledRejection', error => {
|
||||
throw new Error (`Unhandled promise rejection: ${error .toString ()}`);
|
||||
});
|
||||
|
||||
const os = getSystem()
|
||||
|
||||
const plaformKey = os === "linux" ? "LINUX" : os === "mac" ? "MAC" : "WINDOWS"
|
||||
|
||||
builder.build ({ config, targets: builder.Platform[plaformKey].createTarget () })
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @type {import ("electron-builder") .Configuration}
|
||||
*/
|
||||
module.exports = {
|
||||
appId: "com.lukasobermann.optolithinsider",
|
||||
productName: "Optolith Insider",
|
||||
copyright: "This product was created under a license. Das Schwarze Auge and its logo as well as Aventuria, Dere, Myranor, Riesland, Tharun and Uthuria and their logos are trademarks of Significant GbR. The title and contents of this book are protected under the copyright laws of the United States of America. No part of this publication may be reproduced, stored in retrieval systems or transmitted, in any form or by any means, whether electronic, mechanical, photocopy, recording, or otherwise, without prior written consent by Ulisses Spiele GmbH, Waldems. This publication includes material that is protected under copyright laws by Ulisses Spiele and/or other authors. Such material is used under the Community Content Agreement for the SCRIPTORIUM AVENTURIS. All other original materials in this work is copyright 2017-present by Lukas Obermann and published under the Community Content Agreement for the SCRIPTORIUM AVENTURIS.",
|
||||
directories: {
|
||||
output: "dist/insider"
|
||||
},
|
||||
files: [
|
||||
"app/**/*",
|
||||
"LICENSE"
|
||||
],
|
||||
asarUnpack: "app/Database/**/*",
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: "nsis",
|
||||
arch: [
|
||||
"x64",
|
||||
"ia32"
|
||||
]
|
||||
}
|
||||
],
|
||||
icon: "app/icon.ico",
|
||||
artifactName: "OptolithInsiderSetup_${version}.${ext}"
|
||||
},
|
||||
nsis: {
|
||||
perMachine: true,
|
||||
differentialPackage: true,
|
||||
deleteAppDataOnUninstall: false,
|
||||
},
|
||||
linux: {
|
||||
category: "RolePlaying",
|
||||
executableName: "OptolithInsider",
|
||||
icon: "app",
|
||||
target: [
|
||||
{
|
||||
target: "AppImage",
|
||||
arch: [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
target: "tar.gz",
|
||||
arch: [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
artifactName: "OptolithInsider_${version}.${ext}"
|
||||
},
|
||||
mac: {
|
||||
category: "public.app-category.role-playing-games",
|
||||
type: "distribution",
|
||||
icon: "app/icon.icns",
|
||||
target: "default",
|
||||
artifactName: "OptolithInsider_${version}.${ext}",
|
||||
hardenedRuntime: true,
|
||||
// entitlements: "deploy/entitlements.mac.plist",
|
||||
// entitlementsInherit: "deploy/entitlements.mac.plist",
|
||||
gatekeeperAssess: false,
|
||||
},
|
||||
dmg: {
|
||||
sign: false,
|
||||
},
|
||||
publish: {
|
||||
"provider": "generic",
|
||||
"url": `${process.env.UPDATE_URL}/insider/\${os}`,
|
||||
"channel": "latest"
|
||||
},
|
||||
// afterSign: notarize,
|
||||
}
|
||||
+129
-72
@@ -1,7 +1,22 @@
|
||||
// @ts-check
|
||||
import { join } from "node:path"
|
||||
import packageJson from "../package.json" assert { type: "json" }
|
||||
import { run, upload } from "./uploader.js"
|
||||
require("dotenv").config()
|
||||
const { Client } = require ("qusly-core")
|
||||
const readline = require ("readline")
|
||||
const fs = require ("fs")
|
||||
const path = require ("path")
|
||||
const semver = require ("semver");
|
||||
const { getSystem, getSystemName } = require("./platform");
|
||||
|
||||
const [channel] = process.argv.slice(2)
|
||||
const os = getSystem()
|
||||
|
||||
if (channel !== "stable" && channel !== "prerelease") {
|
||||
throw new TypeError (`publishToServer requires a specified channel ("stable" or "prerelease"), but it received ${channel}`)
|
||||
}
|
||||
|
||||
if (os !== "win" && os !== "mac" && os !== "linux") {
|
||||
throw new TypeError (`publishToServer requires a specified OS ("win", "mac" or "linux"), but it received ${os}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed env variables:
|
||||
@@ -9,85 +24,127 @@ import { run, upload } from "./uploader.js"
|
||||
* - `USERNAME`
|
||||
* - `PASSWORD`
|
||||
* - `ROOT`
|
||||
*
|
||||
* Optional env variables:
|
||||
* - `CI`
|
||||
*/
|
||||
const { HOST, USERNAME, PASSWORD, ROOT = "/", CI } = process.env
|
||||
const uploadToServer = async () => {
|
||||
console.log (`Preparing to upload update files for "${getSystemName(os)}" on "${channel}" channel...`)
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const subFolder = os === "win" ? "win" : os === "linux" ? "linux" : "mac"
|
||||
|
||||
// Detect channel
|
||||
const updateYmlName =
|
||||
os === "win"
|
||||
? "latest.yml"
|
||||
: os === "linux"
|
||||
? "latest-linux.yml"
|
||||
: "latest-mac.yml"
|
||||
|
||||
if (!args.includes("--stable") && !args.includes("--prerelease")) {
|
||||
throw new TypeError(`Missing channel argument (either "--stable" or "--prerelease")`)
|
||||
}
|
||||
const distPath = channel === "prerelease" ? ["dist", "insider"] : ["dist"]
|
||||
|
||||
const isPrerelease = args.includes("--prerelease")
|
||||
const serverPath = `${process.env.ROOT}${channel === "prerelease" ? "/insider" : ""}/${subFolder}`
|
||||
|
||||
console.log(`Detected channel: ${isPrerelease ? "prerelease" : "stable"}`)
|
||||
const regex =
|
||||
channel === "prerelease"
|
||||
? os === "win"
|
||||
? /^OptolithInsiderSetup_(.+)\.exe$/
|
||||
: os === "linux"
|
||||
? /^OptolithInsider_(.+)\.(?:AppImage|tar\.gz)$/
|
||||
: /^OptolithInsider_(.+)\.(?:zip|dmg)$/
|
||||
: os === "win"
|
||||
? /^OptolithSetup_(.+)\.exe$/
|
||||
: os === "linux"
|
||||
? /^Optolith_(.+)\.(?:AppImage|tar\.gz)$/
|
||||
: /^Optolith_(.+)\.(?:zip|dmg)$/
|
||||
|
||||
// Detect OS
|
||||
const recreateFileNames =
|
||||
channel === "prerelease"
|
||||
? os === "win"
|
||||
? /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`OptolithInsiderSetup_${v}.exe`, `OptolithInsiderSetup_${v}.exe.blockmap`]
|
||||
: os === "linux"
|
||||
? /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`OptolithInsider_${v}.AppImage`, `OptolithInsider_${v}.tar.gz`]
|
||||
: /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`OptolithInsider_${v}.dmg`, `OptolithInsider_${v}.dmg.blockmap`, `OptolithInsider_${v}.zip`]
|
||||
: os === "win"
|
||||
? /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`OptolithSetup_${v}.exe`, `OptolithSetup_${v}.exe.blockmap`]
|
||||
: os === "linux"
|
||||
? /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`Optolith_${v}.AppImage`, `Optolith_${v}.tar.gz`]
|
||||
: /**
|
||||
* @param {string} v Version
|
||||
*/ v => [`Optolith_${v}.dmg`, `Optolith_${v}.dmg.blockmap`, `Optolith_${v}.zip`]
|
||||
|
||||
let os
|
||||
let osName
|
||||
const allFiles = await fs.promises.readdir (path.join (...distPath))
|
||||
|
||||
if (process.argv.includes("--linux") || process.platform === "linux") {
|
||||
os = /** @type {const} */ ("linux")
|
||||
osName = "Linux"
|
||||
} else if (process.argv.includes("--mac") || process.platform === "darwin") {
|
||||
os = /** @type {const} */ ("mac")
|
||||
osName = "macOS"
|
||||
} else if (process.argv.includes("--win") || process.platform === "win32") {
|
||||
os = /** @type {const} */ ("win")
|
||||
osName = "Windows"
|
||||
} else {
|
||||
throw new TypeError(`The target operating system cannot be inferred from the environment.`)
|
||||
}
|
||||
const allInstallerNames = allFiles .filter (fileName => regex .test (fileName))
|
||||
|
||||
console.log(`Detected operating system: ${osName}`)
|
||||
const allInstallerVersionsSorted =
|
||||
allInstallerNames .map (fileName => regex .exec (fileName) [1])
|
||||
.sort (semver .rcompare)
|
||||
|
||||
// Infer files to upload
|
||||
|
||||
let fileExtensions
|
||||
let updateInfoFileName
|
||||
|
||||
switch (os) {
|
||||
case "win":
|
||||
fileExtensions = [".exe", ".exe.blockmap"]
|
||||
updateInfoFileName = "latest.yml"
|
||||
break
|
||||
case "linux":
|
||||
fileExtensions = [".AppImage", ".tar.gz"]
|
||||
updateInfoFileName = "latest-linux.yml"
|
||||
break
|
||||
case "mac":
|
||||
fileExtensions = [".dmg", ".dmg.blockmap", ".zip", ".zip.blockmap"]
|
||||
updateInfoFileName = "latest-mac.yml"
|
||||
break
|
||||
}
|
||||
|
||||
const fileNames = [
|
||||
...fileExtensions.map(ext => {
|
||||
const chPart = isPrerelease ? "Insider" : ""
|
||||
const osPart = os === "win" ? "Setup" : ""
|
||||
return `Optolith${chPart}${osPart}_${packageJson.version}${ext}`
|
||||
}),
|
||||
updateInfoFileName,
|
||||
]
|
||||
|
||||
console.log(`Files to upload: ${fileNames.join(", ")}.`)
|
||||
|
||||
// Upload files
|
||||
|
||||
const localDir = isPrerelease ? join("dist", "insider") : join("dist")
|
||||
const remoteDir = join(ROOT, isPrerelease ? "insider" : ".", os)
|
||||
|
||||
await run({ host: HOST, username: USERNAME, password: PASSWORD }, async client => {
|
||||
for (const fileName of fileNames) {
|
||||
console.log(`Uploading ${fileName} ...`)
|
||||
await upload(client, localDir, remoteDir, fileName)
|
||||
if (allInstallerVersionsSorted .length < 1) {
|
||||
throw new RangeError ("Array of sorted installer versions is empty")
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Uploading files finished successfully.`)
|
||||
const latestFileNames = recreateFileNames (allInstallerVersionsSorted [0])
|
||||
|
||||
console.log(`Files to upload: ${[updateYmlName, ...latestFileNames] .join (", ")}.`);
|
||||
|
||||
// Actual server connection
|
||||
|
||||
const client = new Client ()
|
||||
|
||||
console.log("Connecting to server...");
|
||||
|
||||
await client.connect ({
|
||||
protocol: "sftp",
|
||||
host: process.env.HOST,
|
||||
user: process.env.USERNAME,
|
||||
password: process.env.PASSWORD,
|
||||
})
|
||||
|
||||
let loggedProgress = false
|
||||
|
||||
client.addListener("progress", (progress, info) => {
|
||||
if (loggedProgress) {
|
||||
readline.clearLine(process.stdout, 1);
|
||||
readline.cursorTo(process.stdout, 0);
|
||||
}
|
||||
else {
|
||||
loggedProgress = true
|
||||
}
|
||||
|
||||
process.stdout.write(`Progress: ${progress.percent}%`);
|
||||
})
|
||||
|
||||
console.log(`Server connection established.`);
|
||||
|
||||
for (const fileName of latestFileNames) {
|
||||
console.log(`Uploading ${fileName}`);
|
||||
|
||||
loggedProgress = false
|
||||
const stream = fs.createReadStream (path.join (...distPath, fileName))
|
||||
await client.upload (`${serverPath}/${fileName}`, stream)
|
||||
|
||||
console.log(`\nUpload done: ${fileName}.`);
|
||||
}
|
||||
|
||||
console.log(`Uploading ${updateYmlName}...`);
|
||||
|
||||
loggedProgress = false
|
||||
const updateYml = fs.createReadStream (path.join (...distPath, updateYmlName))
|
||||
await client.upload (`${serverPath}/${updateYmlName}`, updateYml)
|
||||
|
||||
console.log(`\nUpload done: ${updateYmlName}.`);
|
||||
|
||||
await client.disconnect ()
|
||||
|
||||
console.log("Closed server connection.");
|
||||
}
|
||||
|
||||
uploadToServer()
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.debugger</key>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,29 @@
|
||||
// @ts-check
|
||||
|
||||
const { notarize } = require ('electron-notarize')
|
||||
|
||||
/**
|
||||
* @param context {import("electron-builder").AfterPackContext}
|
||||
*/
|
||||
exports.notarize = async context => {
|
||||
const { electronPlatformName, appOutDir } = context
|
||||
|
||||
if (electronPlatformName === 'darwin') {
|
||||
const appName = context.packager.appInfo.productFilename
|
||||
|
||||
console.log (`Notarizing "${appName}.app"...`)
|
||||
|
||||
await notarize ({
|
||||
appBundleId: 'com.lukasobermann.optolith',
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLEID,
|
||||
appleIdPassword: process.env.APPLEIDPASS,
|
||||
})
|
||||
|
||||
console.log (`Notarization successful`)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
const os = require ("os")
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @typedef {"win" | "mac" | "linux"} System
|
||||
* @returns {System}
|
||||
*/
|
||||
getSystem: () => {
|
||||
switch (os.platform()) {
|
||||
case "win32":
|
||||
return "win"
|
||||
case "darwin":
|
||||
return "mac"
|
||||
default:
|
||||
return "linux"
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {System} system
|
||||
*/
|
||||
getSystemName: system => {
|
||||
switch (system) {
|
||||
case "win":
|
||||
return "Windows"
|
||||
case "mac":
|
||||
return "macOS"
|
||||
case "linux":
|
||||
return "Linux"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// @ts-check
|
||||
import { join } from "path"
|
||||
import { join as joinPosix } from "path/posix"
|
||||
import Client from "ssh2-sftp-client"
|
||||
|
||||
/**
|
||||
* Create a new sftp/ssh client and use it for the callback. The connection will
|
||||
* be cleaned up afterwards.
|
||||
* @param {Client.ConnectOptions} options
|
||||
* @param {(client: Client) => Promise<void>} action
|
||||
*/
|
||||
export const run = async (options, action) => {
|
||||
const client = new Client()
|
||||
console.log("Connecting to server ...")
|
||||
await client.connect(options)
|
||||
console.log(`Server connection established.`)
|
||||
await action(client)
|
||||
await client.end()
|
||||
console.log("Closed server connection.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file from a local directory to a remote directory using the given
|
||||
* client.
|
||||
* @param {Client} client
|
||||
* @param {string} localDir
|
||||
* @param {string} remoteDir
|
||||
* @param {string} fileName
|
||||
*/
|
||||
export const upload = async (client, localDir, remoteDir, fileName) => {
|
||||
console.log(`Uploading ${fileName} ...`)
|
||||
|
||||
let didStep = false
|
||||
|
||||
await client.fastPut(join(localDir, fileName), joinPosix(remoteDir, fileName), {
|
||||
step: !process.env.CI
|
||||
? (totalTransferred, _, total) => {
|
||||
if (didStep) {
|
||||
process.stdout.clearLine(0)
|
||||
process.stdout.cursorTo(0)
|
||||
} else {
|
||||
didStep = true
|
||||
}
|
||||
const percent = Math.floor((totalTransferred / total) * 100)
|
||||
process.stdout.write(`Progress: ${percent}%`)
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
|
||||
console.log(`Upload done: ${fileName}`)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
# Decoding
|
||||
|
||||
Here you'll find some useful information about the design of the YAML decoders.
|
||||
|
||||
## Overview
|
||||
|
||||
Decoding the database is splitted into parsing the YAML files and decoding the result. Parsing is done by the `Yaml_Parse` module and the `Yaml_Decode` module controls the decoding of the various entry types. The `Yaml` module then combines all functions into a single exported function.
|
||||
|
||||
## Decode Entry Types
|
||||
|
||||
### Decoder structure
|
||||
|
||||
All decodable main types share a common decoder layout. All values only relevant while decoding are inside a `Decode` module at the same level as the type it is the decoder for.
|
||||
|
||||
```ml
|
||||
(** Since this is the actual type, it's not really part of the decoding pipeline
|
||||
but I'll keep it here to make the definitions complete. *)
|
||||
type t = {
|
||||
(* ... *)
|
||||
}
|
||||
|
||||
module Decode = struct
|
||||
open Json.Decode
|
||||
open JsonStrict
|
||||
|
||||
type translation = { (* ... language-specific properties *) }
|
||||
|
||||
let translation json =
|
||||
{
|
||||
(* ... decode language-specific properties *)
|
||||
}
|
||||
|
||||
type multilingual = {
|
||||
(* ... language-independent properties *)
|
||||
translations : translation TranslationMap.t;
|
||||
}
|
||||
|
||||
let multilingual json =
|
||||
{
|
||||
(* ... decode language-independent properties *)
|
||||
translations =
|
||||
json |> field "translations" (TranslationMap.Decode.t translation);
|
||||
}
|
||||
|
||||
let make_assoc locale_order json =
|
||||
let open Option.Infix in
|
||||
json |> multilingual |> fun multilingual ->
|
||||
multilingual.translations |> TranslationMap.preferred locale_order
|
||||
<&> fun (translation : translation) ->
|
||||
( multilingual.id,
|
||||
{
|
||||
(* ... merge language-independent and language-specific properties *)
|
||||
} )
|
||||
end
|
||||
```
|
||||
|
||||
#### Decoding types
|
||||
|
||||
Since language-specific values in the YAML files are always defined in dictionaries where the keys map to the locale identifiers, we always split the language-specific and the language-independent parts.
|
||||
|
||||
The language-specific part of a type is always defined as type `translation`. The corresponding `translation` decoder function is later used to decode the translation dictionary, also called *translation map*.
|
||||
|
||||
The language-independent part of a type is defined as type `multilingual` and usually has the property `translations`, which is the dictionary of all translations. The corresponding `multilingual` decoder function uses the `translation` decoder together with the translation map decoder from the `TranslationMap` module to fully decode the dictionary of translations.
|
||||
|
||||
Using a list of preferred locales, sorted by preference, (`Locale.Order.t`) we can retrieve the most preferred translation using `TranslationMap.preferred` and then combine the translation values and the multilingual values to form the final record type. Following OCaml conventions, this function is called `make`.
|
||||
|
||||
Often, in order to be used more easily, the `make` function returns a pair of the respective entry's identifier and the entry itself to create a `Map` more easily. To hint at this specific use, the function is then called `make_assoc`. If a nested type is only decoded in a context of collection type (like a list), this decoder can be provided using a `make` variant as well (in this case, `make_list`).
|
||||
|
||||
If other decodable types are nested withing a type, the `multilingual` function usually takes the list of preferred locales as well to be able to pass it down to the nested types' decoders.
|
||||
|
||||
### `Decode` module visibility conventions
|
||||
|
||||
Only the `make` function or its variant should be exposed.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- The language-specific tyüe is always named `translation`.
|
||||
- The record containing all language-independent values as well as the translation map is called `multilingual` and the translation map is at the property `translations` (just like in the source).
|
||||
- A decoder for a specific type has the same name as the type.
|
||||
- The function resolving the translation map and returning the actual main type is called `make`.
|
||||
- A potential helper function that decodes the type as part of a collection is called `make_c`, where `c` is the name of the collection, e.g. `make_list`.
|
||||
@@ -0,0 +1,8 @@
|
||||
# *Dynamic* and *Static*
|
||||
|
||||
In entry type definition files, you'll often see the content is wrapped in a `Dynamic` and a `Static` module. What does that mean?
|
||||
|
||||
- **Static** values come from the YAML files, so they have been defined there and they will not change while the program runs.
|
||||
- **Dynamic** values are kind of like *instances* of the *static* values. They are part of the heroes and define how an entry is configured for that hero.
|
||||
|
||||
For example, a `Spell.Static.t` type is the static representation of a spell: It contains it's name, IC, duration, target category, and so on. The `Spell.Dynamic.t` type is the dynamic representation of the spell for a specific hero: Is the spell active? If yes, which SR does it have?
|
||||
@@ -0,0 +1,27 @@
|
||||
# Generating module code
|
||||
|
||||
## Convert syntax
|
||||
|
||||
Using `bsrefmt`, you can convert between syntaxes:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsrefmt path/to/file --print=syntax
|
||||
```
|
||||
|
||||
`syntax` can be `res`, `re` and `ml`. The converted code from the referenced file will be output to command line.
|
||||
|
||||
## Interface files
|
||||
|
||||
Using `bsc`, you can generate an interface file in OCaml syntax if only the implementation file is present:
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
To generate in Reason syntax, provide the `-bs-re-out` flag.
|
||||
|
||||
```shell
|
||||
./node_modules/.bin/bsc -bs-re-out ./lib/bs/path/to/file.cmi
|
||||
```
|
||||
|
||||
The generated code will be output to command line.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Labelled arguments syntax in OCaml
|
||||
|
||||
The syntax for labelled arguments might not always be obvious. Below you'll find a cheatsheet for the syntax in both implementations and signatures.
|
||||
|
||||
## Basic
|
||||
|
||||
### Definition
|
||||
|
||||
```ocaml
|
||||
let f ~param:param = param
|
||||
```
|
||||
|
||||
You can omit the variable name after the label itself if its the same as the label.
|
||||
|
||||
```ocaml
|
||||
let f ~param = param
|
||||
```
|
||||
|
||||
### Call
|
||||
|
||||
```ocaml
|
||||
let x = f ~param:value
|
||||
```
|
||||
|
||||
### Signature
|
||||
|
||||
```ocaml
|
||||
val f : ~param:'a -> 'a
|
||||
```
|
||||
|
||||
## Optional
|
||||
|
||||
```ocaml
|
||||
let f ?param:param () = param
|
||||
let f ?param () = param
|
||||
|
||||
val f : ?param:'a -> unit -> 'a
|
||||
```
|
||||
|
||||
You can also provide a default value.
|
||||
|
||||
```ocaml
|
||||
let f ?(param=0) () = param
|
||||
let f ?param:(param=0) () = param
|
||||
```
|
||||
|
||||
A function defining optional arguments must also define at least one non-labelled argument. A typical parameter for this use case is unit.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ~param:0 () (* x = Some 0 *)
|
||||
```
|
||||
|
||||
You may also forward an optional type to an optional argument.
|
||||
|
||||
```ocaml
|
||||
let f ?param () = param
|
||||
|
||||
let x = f () (* x = None *)
|
||||
let x = f ?param:None () (* x = None *)
|
||||
let x = f ?param:(Some 0) () (* x = Some 0 *)
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
# Structure
|
||||
|
||||
- `src` – The source code main folder, containing program entry points.
|
||||
- `Common` – Types and functions associated with multiple ‘namespaces’.
|
||||
- `Database` – The flat-file database containing all static data from the source books.
|
||||
- `Dependencies` – Types and functions for working with dependencies between entries and the prerequisites they are based on.
|
||||
- `External` – Bindings to external libraries via FFI.
|
||||
- `Localization` – Types and functions related to different languages in the app and the source books.
|
||||
- `Rated` – All data types for entries with *ratings* (attributes, skills or combat techniques) and related types and logic.
|
||||
- `ArcaneSpellworks` – All data types for arcane spellworks (spells and rituals) and related types and logic.
|
||||
- `Liturgies` – All data types for liturgies (liturgical chants and ceremonies) and related types and logic.
|
||||
- `MagicalActions` – All data types for magical actions (curses, Elven magical songs, …) and related types and logic.
|
||||
- `Rules` – All data types for rule entries.
|
||||
- `Sources` – Types and functions dealing with data related to source books: Source books themselves, references, availability by book and errata.
|
||||
- `Utilities` – Utility functions and types for working with standard types or I/O.
|
||||
- `Abstractions` – Utility functions and types for working with type abstractions such as monads that can be used to create similar functions for different container types.
|
||||
@@ -1,88 +1,3 @@
|
||||
## 1.5.1
|
||||
|
||||
### Verbesserungen
|
||||
|
||||
* **MapTool:** unter dem Exportbutton sind nun Links zu der MapTool-Erweiterung ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Fehlerbehebungen
|
||||
|
||||
* **MapTool:** ohne Avatar konnte nicht exportiert werden ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), schließt [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** für MapTool Version 1.11.4 wurde das Exportformat angepasst ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* Vorteil *Vertrauenerweckend* konnte selbst mit einem aktiven Nachteil *Inkompetent* in einem Gesellschaftstalent gekauft werden ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), schließt [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Voraussetzungen, die hätten durchgestrichen werden sollen, wurden stattdessen mit umgebenen Tilden dargestellt ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), schließt [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Features
|
||||
|
||||
* MapTool-Export ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Portugiesische Übersetzung ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Native Unterstützung für Apples M1 ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Verbesserungen
|
||||
|
||||
* Sicherheit ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) and others)
|
||||
|
||||
### Fehlerbehebungen
|
||||
|
||||
* Der Trefferzonenrüstungscharakterbogen hatte einen höheren Abstand nach links als die anderen Bögen ([e637136](https://github.com/elyukai/optolith-client/commit/e63713687eff9b825a79d159fb8b44dc26f64ffe), [#788](https://github.com/elyukai/optolith-client/issues/788))
|
||||
* Bei hinzugekaufter LE wurde nicht für KO ein entsprechendes Minimum gesetzt, sondern für KK ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Waffenindikatoren für zweihändig (2H) und improvisiert (i) fehlten auf dem Charakterbogen ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Waffen der Kampftechnik Raufen hatten auf dem Charakterbogen keinen Paradewert ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* Ein Eintrag, der von einer einzelnen Aktivierung eines anderen Eintrags abhängt, hat die Möglichkeit, die Aktivierung zu entfernen, auch bei mehreren Aktivierungen dieses anderen Eintrags nicht bereitgestellt ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Einträge, die Fremdzauber in die eigenen Tradition überführen, konnten entfernt werden, wenn für diese Zauber Zaubererweiterungen gekauft worden waren ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* Der Infobereich für Gegenstände hat manchmal nur Fehler angezeigt ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Professionen, die höhere Kampftechnikwerte geben als durch den Erfahrungsgrad erlaubt, wurden nicht ausgeblendet ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* Bei der Sonderfertigkeit Kernschuss war unter Erschwernis ein Minuszeichen falsch platziert ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#1080](https://github.com/elyukai/optolith-client/issues/1080))
|
||||
* In der Beschreibung von Albernisches Entermesser und Harbener Säbel war die Seitenzahl falsch ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#1081](https://github.com/elyukai/optolith-client/issues/1081))
|
||||
* In der Beschreibung des Zaubers Zauberschnurren waren die QS-Beschreibungen vertauscht ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#846](https://github.com/elyukai/optolith-client/issues/846) [#1122](https://github.com/elyukai/optolith-client/issues/1122))
|
||||
* Schlachtermesser und Fleischerbeil waren nicht als improvisierte Waffen markiert ([ccd7dec](https://github.com/elyukai/optolith-client/commit/ccd7decc8314ca476b6a15932ed9b3fb1c22a1f7), [#808](https://github.com/elyukai/optolith-client/issues/808))
|
||||
* INI/GS-Modifikatoren von Rüstungen wurden negativ eingerechnet ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Sprachspezialisierungen kosteten nicht immer 1 AP und konnten zudem pro Sprache nicht mehrfach aktiviert werden ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Portraits mit transparenten Flächen hatten einen schwarzen Hintergrund ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* Bei Profession Garether Krieger fehlte Schwerter auf 12 und Orientierung sollte 3 statt 2 betragen ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#792](https://github.com/elyukai/optolith-client/issues/792))
|
||||
* Das bornländische Kulturpaket führte Lederbearbeitung statt Lebensmittelbearbeitung auf ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* Zauber Wirbelform hatte die falsche Tradition und eine falsche Reihenfolge bei den Zaubererweiterungen ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#1139](https://github.com/elyukai/optolith-client/issues/1139))
|
||||
* Kleine Fehler bei den Maßangaben in der Beschreibung des Zaubers Invocatio Minima ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#1224](https://github.com/elyukai/optolith-client/issues/1224))
|
||||
* Leiteigenschaftsänderungen bei Gegenständen wurden auf dem Charakterbogen geändert, aber nicht direkt im Editierfenster des Gegenstands ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Bestimmte Professionen waren trotz eines für vorausgesetzte Eigenschaften zu niedrigen Erfahrungsgrads auswählbar ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Herbeirufende und beschwörende Zauber aus Professionen haben nicht direkt die Abhängigkeiten für ihre Voraussetzungen registriert ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* Das Gewicht bei Elfen wurde nicht korrekt zufällig generiert ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* Schreibfehler bei Physiostabilis ([e40f995](https://github.com/elyukai/optolith-client/commit/e40f995e341dc7fbcb1d9688c4b82a9187c180de), [#1234](https://github.com/elyukai/optolith-client/issues/1234))
|
||||
* Tradition (Druide) referenzierte einen nicht existierenden Nachteil statt des Vorteils Eisenaffine Aura ([f8114c2](https://github.com/elyukai/optolith-client/commit/f8114c2f4318967631b71ea3614bf647e7826fa2), [#1237](https://github.com/elyukai/optolith-client/issues/1237))
|
||||
* Der Hakendolch war nicht als Parierwaffe markiert ([ed74170](https://github.com/elyukai/optolith-client/commit/ed74170865b2caad8008a345db5c1705e0376bc5), [#1232](https://github.com/elyukai/optolith-client/issues/1232))
|
||||
* Der Waffenvorteil des Kriegsbogens hatte einen falschen Schadensbonus ([8297455](https://github.com/elyukai/optolith-client/commit/8297455ddbd360035428a4ed244a684e6f1c7e7a), [#1074](https://github.com/elyukai/optolith-client/issues/1074))
|
||||
* Profession Spinnenhexe hatte einen falschen Tierkunde-Wert ([#1250](https://github.com/elyukai/optolith-client/issues/1250))
|
||||
* Eigenschaften auf dem niedrigsten möglichen Wert (8) erlaubten Kampftechniken, dessen Leiteigenschaften sie waren, keinen Kampftechnikwert über 6 ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* Profession Sharisad bekam einen Bonus auf Steinbearbeitung statt auf Stoffbearbeitung ([cd16e53](https://github.com/elyukai/optolith-client/commit/cd16e5314a7f579a5f5f916bb204a15df3220bda), [#1341](https://github.com/elyukai/optolith-client/issues/1341))
|
||||
* Die PDF-Datei war zu groß, wenn die Hintergrundtextur verwendet wurde ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* Bei AppImages funktionierte der Auto-Updater nicht ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* SF Weg der Schreiberin konnte nicht aktiviert werden ([83b534d](https://github.com/elyukai/optolith-client/commit/83b534d3f9fcd5e63ef7cf482d9dd26f06b5a1a2), [#1233](https://github.com/elyukai/optolith-client/issues/1233))
|
||||
* Einige Leiteigenschaftsvoraussetzungen wurden ignoriert ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Die Texteingabefehler für die Reichweite von Fernkampfwaffen beeinflussten ihre Inhalte gegenseitig ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* Wenn eine einzige Sprache und dazu passende Schrift vorausgesetzt wurden, wurden stattdessen alle Sprachen und Schriften vorausgesetzt ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Schriften erforderten keine aktive passende Sprache ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Einige Gegenstandswerte wurden nicht gespeichert ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* Schreibfeler bei Tarnele ([8b5eede](https://github.com/elyukai/optolith-client/commit/8b5eedeb372622761480f2511935e8b74bdfcdcd), [#1330](https://github.com/elyukai/optolith-client/issues/1330))
|
||||
* Die Auswahl eines Fremdzaubers für eine gildenmagische Profession war optional ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* Kampftechniken haben nicht das Minimum ihrer Leiteigenschaften beeinflusst ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* Das Speichern eines Charakters hat manchmal die Änderungshistorie von mehreren Charakteren gelöscht ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* Zauber, die durch Imitationszauberei in Schelmenstreiche konvertiert wurden, konnten Zaubererweiterungen nutzen ([e558c1d](https://github.com/elyukai/optolith-client/commit/e558c1d80d1c668f926e4a56049029f989081a45), [#828](https://github.com/elyukai/optolith-client/issues/828))
|
||||
* Dickere Zeilen in der zweiten Spalte der Talenttabelle auf dem Charakterbogen ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
* Sonderfertigkeit Blattschuss zeigte den falschen Kampfsonderfertigkeitstyp an ([7e31136](https://github.com/elyukai/optolith-client/commit/7e31136083ae62e26c5017f3af2376938bd264c3), [#1331](https://github.com/elyukai/optolith-client/issues/1331))
|
||||
* Schreibfehler bei Tradition (Scharlatane) ([bdb3eb7](https://github.com/elyukai/optolith-client/commit/bdb3eb7804f8464689172f39d5bd8c229140c891), [#1239](https://github.com/elyukai/optolith-client/issues/1239))
|
||||
|
||||
### Danke
|
||||
|
||||
Zu guter Letzt ein großes **Danke** an alle, die über verschiedeste Kanäle Feedback geben und helfen, sowie an die folgenden Menschen auf GitHub, die an dieser Version mitgewirkt haben!
|
||||
|
||||
- [Elidia (@FloraMayr)](https://github.com/FloraMayr)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lector (@Lector)](https://github.com/Lector)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [Marc Schwering (@m4rcsch)](https://github.com/m4rcsch)
|
||||
- [Pepelios (@Pepelios)](https://github.com/Pepelios)
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Enhancements
|
||||
@@ -51,9 +51,5 @@ Die Charaktere und auch andere Dateien wie die Konfiguration findest du – je n
|
||||
Hier finden sich verschiedene relevante Dateien:
|
||||
|
||||
- `heroes.json` enthält alle Helden, die aktuell in Optolith sind
|
||||
- `config.json` enthält die Konfiguration deiner Optolith-Anwendung. Enthält keine wichtigen Daten, nur Dinge wie Farbschema, Sprache (falls man eine spezielle eingestellt hat), und ein paar andere Sachen.
|
||||
- `config.json` enthält die Konfiguration deiner Optolith-Anwendung. Enthält keine wichtigen Daten, nur Dinge wie Farbschema, Sprache (falls man eine spezielle eingestellt hat), und ein paar andere Sachen.
|
||||
*Ein Hinweis zu Windows:* `AppData` ist ein versteckter Ordner. Um ihn zu sehen, müsst ihr im Windows Explorer unter *Ansicht* die Einstellung *Ausgeblendete Elemente* aktivieren.
|
||||
|
||||
## Wie skaliere ich die Benutzeroberfläche von Optolith auf meinem Linux-System?
|
||||
|
||||
Wenn du die `tar.gz` heruntergeladen hast, kannst du in den entpackten Ordner navigieren und dort `GDK_DPI_SCALE=<Scale> ./Optolith` in der Kommandozeile ausführen. Dabei muss `<Scale>` mit dem gewünschten Skalierungsfaktor ersetzt werden, also z. B. `1.5`.
|
||||
@@ -1,82 +1,3 @@
|
||||
## 1.5.1
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **MapTool:** add links to extension next to export button ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MapTool:** without an avatar no export was possible ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), closes [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** changed export format for MapTool version 1.11.4 ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
<!-- * **MapTool:** Add missing Italian translations for export ([3f87dca](https://github.com/elyukai/optolith-client/commit/3f87dca910ce5229c7fd5378ba5f4177cbf431ac)), closes [#1350](https://github.com/elyukai/optolith-client/issues/1350) -->
|
||||
* Advantage *Inspire Confidence* could be bought with an active *Incompetent* in a social skill ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), closes [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Strikethrough prerequisites were rendered with surrounding tildes instead ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), closes [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Features
|
||||
|
||||
* MapTool export ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Portuguese translations ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Native Apple M1 support ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Kilogram input hint for weight in item dialog ([bd9fc1b](https://github.com/elyukai/optolith-client/commit/bd9fc1badc8a408495bd7c1594338b1a5fe6df11))
|
||||
* Security ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) and others)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Hit Zone Armor character sheet had left margin ([e637136](https://github.com/elyukai/optolith-client/commit/e63713687eff9b825a79d159fb8b44dc26f64ffe), [#788](https://github.com/elyukai/optolith-client/issues/788))
|
||||
* Additional LE set a minimum required value of STR instead of CON ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Two-handed (2H) and improvised (i) weapon indicators were missing on character sheet ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Brawling weapons had no parry on character sheet ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* An entry that depends on a single activation of another entry disables removing of all activations of that entry if multiple activations are present ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Entries affecting spells to not be unfamiliar could be removed if Spell Enhancements were bought ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* The equipment info area may have crashed ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Professions that granted higher Combat Technique Ratings than what the selected Experience Level allowed were not filtered ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* INI/MOV modifiers on armor were handled as penalties ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Language specializations did not always cost 1 AP and could not be activated multiple times for a single language ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Portraits with transparency had a black background ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* Bornlander cultural package had Leatherworking instead of Prepare Food ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* Spell Wind Shaping had the wrong traditions and wrong spell enhancements order ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#1139](https://github.com/elyukai/optolith-client/issues/1139))
|
||||
* Primary attribute changes for items were not applied in edit window, only on character sheet ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Professions with due to EL impossible attribute values could be selected ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Summoning spellworks from professions did not register dependencies from prerequisites ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* Weight of Elves was not generated correctly ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* Gaze into the Essence spell was misspelled as Gase into the Essence ([42891f0](https://github.com/elyukai/optolith-client/commit/42891f070c35b74c49bb2c9e16152789cc8cc7df), [#1180](https://github.com/elyukai/optolith-client/issues/1180))
|
||||
* Tradition (Druid) referenced a non-existent disadvantage instead of advantage Iron-Attuned Aura ([f8114c2](https://github.com/elyukai/optolith-client/commit/f8114c2f4318967631b71ea3614bf647e7826fa2), [#1237](https://github.com/elyukai/optolith-client/issues/1237))
|
||||
* Hook Dagger was not marked as a parrying weapon ([ed74170](https://github.com/elyukai/optolith-client/commit/ed74170865b2caad8008a345db5c1705e0376bc5), [#1232](https://github.com/elyukai/optolith-client/issues/1232))
|
||||
* Special ability Deadly Thrust had special ability Crossblock's rules text ([ec9bd71](https://github.com/elyukai/optolith-client/commit/ec9bd71e101abcaf61e84e3da340890e161896ea), [#841](https://github.com/elyukai/optolith-client/issues/841))
|
||||
* Missing translations for spell enhancements of Heptagramma ([a5df7c8](https://github.com/elyukai/optolith-client/commit/a5df7c863cded391609950fa5b8bf8474ffe2dfd), [#848](https://github.com/elyukai/optolith-client/issues/848))
|
||||
* Attributes on minimum value (8) did not allow a combat technique rating over 6 if the attribute was the combat technique's primary attribute ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* Profession Sharisad got Earthencraft bonus instead of Clothworking bonus ([cd16e53](https://github.com/elyukai/optolith-client/commit/cd16e5314a7f579a5f5f916bb204a15df3220bda), [#1341](https://github.com/elyukai/optolith-client/issues/1341))
|
||||
* The Hit Zone Armor tab in Belongings was not available, even with Aventuria Armory activated ([383a9c1](https://github.com/elyukai/optolith-client/commit/383a9c166c6d8c5d713f4852f40999c5d8c00efc), [#1328](https://github.com/elyukai/optolith-client/issues/1328))
|
||||
* Too large PDF output when using a sheet background ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* Weight column in inventory showed wrong weight ([6ba99f2](https://github.com/elyukai/optolith-client/commit/6ba99f2f4610e8a26d1703d978738a907f26bd92), [#1329](https://github.com/elyukai/optolith-client/issues/1329))
|
||||
* Auto-updater did not work in AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* Some primary attribute prerequisites were not enforced ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Range brackets input fields influenced other bracket fields ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* Requiring only one matching languages and script required all of them instead ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Scripts did not require an active matching language ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Some item properties were not saved ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* Unfamiliar spell selection in profession was optional ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* special ability Knuckle Storm's description used Damage Points instead of Brawling Points ([06b57c9](https://github.com/elyukai/optolith-client/commit/06b57c9d15e1ef6f35da69ea46512bb790119b0a), [#1340](https://github.com/elyukai/optolith-client/issues/1340))
|
||||
* Combat techniques did not influence the minimum of their primary attributes ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* Saving a hero cleared the undo history of multiple characters ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* Widened rows in second column of skills table on character sheet ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to all of you who give feedback and help out on various channels as well as to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [Elidia (@FloraMayr)](https://github.com/FloraMayr)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lector (@Lector)](https://github.com/Lector)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [Marc Schwering (@m4rcsch)](https://github.com/m4rcsch)
|
||||
- [Pepelios (@Pepelios)](https://github.com/Pepelios)
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Enhancements
|
||||
@@ -50,8 +50,4 @@ A list of relevant files:
|
||||
|
||||
- `heroes.json ` contains all heroes saved in Optolith
|
||||
- `config.json` contains the configuration of your Optolith. It does not contain critical data but things like color theme, language (only if you selected a specific one) and alike.
|
||||
*A note on Windows:* `AppData` is a *hidden* folder. To be able to see it, check out the Windows Explorer, go to the *View* tab and activate the setting *Hidden elements*.
|
||||
|
||||
## How do I scale the UI for Optolith on Linux?
|
||||
|
||||
If you have the `tar.gz`, you can go into the extracted folder and then run `GDK_DPI_SCALE=<Scale> ./Optolith`, where `<Scale>` needs to be replaced with the desired scale, e.g. `1.5`.
|
||||
*A note on Windows:* `AppData` is a *hidden* folder. To be able to see it, check out the Windows Explorer, go to the *View* tab and activate the setting *Hidden elements*.
|
||||
@@ -1,69 +1,3 @@
|
||||
## 1.5.1
|
||||
|
||||
### Améliorations
|
||||
|
||||
* **MapTool:** ajout de liens vers l'extension près du bouton d'export ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Bugs corrigés
|
||||
|
||||
* **MapTool:** sans avatar aucun export n'était possible ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), fermeture [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** modification du format d'export pour MapTool en version 1.11.4 ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* L'avantage *Rassurant* pouvait être acheté malgré un désavantage *Incompétent* dans un des talents sociaux ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), fermeture [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Les prérequis à ne pas remplir étaient affichés avec des tirets autour au lieu d'être rayés ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), fermeture [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Fonctionnalités
|
||||
|
||||
* Export MapTool ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Traductions en portugais ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Support natif Apple M1 ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Améliorations
|
||||
|
||||
* Sécurité ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) and others)
|
||||
|
||||
### Bugs corrigés
|
||||
|
||||
* Des PV supplémentaires fixaient une valeur minimale requise de FO au lieu de CN. ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Les indicateurs d'armes à deux mains (2M) et d'armes improvisées (i) manquaient sur la feuille de personnage. ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Les armes de bagarre n'avaient pas de parade sur la feuille de personnage. ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* Une entrée qui dépend d'une seule activation d'une autre entrée désactive la suppression de toutes les activations de cette entrée si des activations multiples sont présentes. ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Les entrées affectant les sorts de façon à ce qu'ils ne soient pas "non familiers" pouvaient être supprimées si des améliorations de sorts étaient achetées. ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* La zone d'information sur l'équipement pouvait planter ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Les professions qui accordaient une valeur de technique de combat supérieure à celle autorisée par le niveau d'expérience sélectionné n'étaient pas filtrées. ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* Les modificateurs INI/VIT sur les armures étaient traités comme des pénalités. ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Les spécialisations linguistiques ne coûtaient pas toujours 1 PA et ne pouvaient pas être activées plusieurs fois pour une même langue. ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Les portraits en transparence avaient un fond noir ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* Le bagage culturel Sourcelandais avait Cuisine au lieu de Travail du cuir. ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* Les changements d'attributs primaires des objets n'étaient pas appliqués dans la fenêtre d'édition, mais uniquement sur la feuille de personnage. ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Les professions avec des valeurs d'attributs impossibles à sélectionner en raison du niveau d'expérience peuvent être sélectionnées. ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Les sorts d'invocation des professions n'enregistraient pas les dépendances des prérequis. ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* Le poids des Elfes n'était pas généré correctement ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* Les attributs ayant une valeur minimale (8) ne permettaient pas d'obtenir une valeur de technique de combat supérieure à 6 si l'attribut était l'attribut principal de la technique de combat. ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* Sortie PDF trop grande lors de l'utilisation d'un fond de feuille. ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* Auto-updater ne fonctionne pas dans AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* Certains prérequis d'attributs primaires n'étaient pas appliqués. ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Les champs de saisie entre parenthèses influençaient les autres champs entre parenthèses. ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* L'exigence d'une seule langue et d'une seule écriture correspondante exigeait toutes les langues à la place ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Les écritures ne requièrent pas qu'une langue correspondante soit active. ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Certaines propriétés d'objets n'étaient pas sauvegardées ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* La sélection des sorts non familiers dans la profession était facultative. ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* Les techniques de combat n'influencent pas le minimum de leurs attributs primaires. ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* La sauvegarde d'un héros efface l'historique d'annulation de plusieurs personnages. ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* Les lignes de la deuxième colonne du tableau des compétences de la fiche de personnage ont été élargies. ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
|
||||
### Remerciements
|
||||
|
||||
Enfin et surtout, un grand **merci** à tous ceux qui font des retours et donnent leur aide sur les différents canaux ainsi qu'aux personnes ci-dessous sur GitHub qui ont contribué à cette version !
|
||||
|
||||
- [Elidia (@FloraMayr)](https://github.com/FloraMayr)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lector (@Lector)](https://github.com/Lector)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [Marc Schwering (@m4rcsch)](https://github.com/m4rcsch)
|
||||
- [Pepelios (@Pepelios)](https://github.com/Pepelios)
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Améliorations
|
||||
@@ -1,20 +0,0 @@
|
||||
# `.icns` file from `.iconset` folder
|
||||
|
||||
```sh
|
||||
iconutil -c icns <PATH_TO_FOLDER>
|
||||
```
|
||||
|
||||
Make sure all of the following files are available. The tool is **case-sensitive**.
|
||||
|
||||
```
|
||||
icon_16.png
|
||||
icon_16@2x.png
|
||||
icon_32.png
|
||||
icon_32@2x.png
|
||||
icon_128.png
|
||||
icon_128@2x.png
|
||||
icon_256.png
|
||||
icon_256@2x.png
|
||||
icon_512.png
|
||||
icon_512@2x.png
|
||||
```
|
||||
@@ -0,0 +1,58 @@
|
||||
## 1.4.2
|
||||
|
||||
### Migliorie
|
||||
|
||||
- Ridotto il tempo di avvio. [#751](https://github.com/elyukai/optolith-client/issues/751)
|
||||
|
||||
### Correzioni
|
||||
|
||||
- Le formule della caratteristiche derivate occupano troppo spazio sul foglio d’identità. [#729](https://github.com/elyukai/optolith-client/issues/729)
|
||||
- Rimozione dei requisiti doppi in alcune abilità speciali. [#730](https://github.com/elyukai/optolith-client/issues/730)
|
||||
- Correzioni minori della localizzazione italiana. [#731](https://github.com/elyukai/optolith-client/issues/731)
|
||||
- Alcune tecniche di combattimento possono essere impostate a valori più elevati di quanto ammesso dal regolamento. [#749](https://github.com/elyukai/optolith-client/issues/749)
|
||||
- Il margine superiore delle finestre in sovrimpressione, come nell’inserimento dei vantaggi, è troppo stretto. [#727](https://github.com/elyukai/optolith-client/issues/727)
|
||||
- Il bonus di impatto non viene calcolato per alcune armi. [#737](https://github.com/elyukai/optolith-client/issues/737)
|
||||
- La professione *Mago bianco (Accademia Garetiana di Spada e Bastone)* dal **Regolamento** ha un valore di PAvv sbagliato e *Astronomia 4* invece di *Alchimia 4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
|
||||
- Campo di testo delle abilità degli animali nel foglio d’identità è troppo piccolo per contenere tutte le abilità. [#733](https://github.com/elyukai/optolith-client/issues/733)
|
||||
- L’abilità speciale *Bladestorm* si applica alle tecniche di combattimento sbagliate. [#768](https://github.com/elyukai/optolith-client/issues/768)
|
||||
- Immagini con estensione in maiuscoletto non vengono accettate come ritratto. [#762](https://github.com/elyukai/optolith-client/issues/762)
|
||||
- Viene mostrata l’icona di caricamento invece del ritratto su fogli d’identità di personaggi senza ritratto. [#759](https://github.com/elyukai/optolith-client/issues/759)
|
||||
- Su Mac, con lo sfondo pergamenato attivato, vengono generate pagine bianche di troppo nel PDF finale. [#748](https://github.com/elyukai/optolith-client/issues/748)
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** a tutti coloro che forniscono commenti, suggerimenti e aiuto sui vari canali sociali e un grazie alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
|
||||
## 1.4.1
|
||||
|
||||
### Correzioni
|
||||
|
||||
- La localizzazione italiana non era disponibile.
|
||||
|
||||
## 1.4.0
|
||||
|
||||
Primo rilascio della versione italiana.
|
||||
|
||||
### API
|
||||
|
||||
Ci sono importanti modifiche in questa nuova versione, che cambiano anche il modo in cui sono memorizzati i dati. Se fai uso dei file memorizzati da Optolith, è possibile fare un salto su [Discord](https://discord.gg/wfdgB9g) in modo da avere più informazioni sulle modifiche nel dettaglio.
|
||||
Siccome sempre più persone fanno uso dei dati di Optolith, da questa versione in poi seguiremo le regole del *versionamento semantico*, in modo da permettere agli utenti di fare affidamento alla compatibilità dei file sorgente e dei file dei dati, verificandone soltanto il numero di versione.
|
||||
Ci sarà un nuovo formato di dati sia per gli eroi memorizzati che per i dati sorgente.
|
||||
Le modifiche non sono ancora finalizzate e la nuova versione maggiore è ancora lontana, ma è consigliato aggiornarsi al nuovo formato di dati il prima possibile.
|
||||
|
||||
### Ringraziamenti
|
||||
|
||||
Come ultimo punto, ma non per importanza, un grandissimo **grazie** alle seguenti persone su GitHub che hanno contribuito a questa versione!
|
||||
|
||||
- [Jonas (@Rahjenaos)](https://github.com/Rahjenaos)
|
||||
- [Jordok (@Jordok)](https://github.com/Jordok)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [manuelstengelberger (@manuelstengelberger)](https://github.com/manuelstengelberger)
|
||||
- [Philipp A. (@flying-sheep)](https://github.com/flying-sheep)
|
||||
- [ZeSandman (@ZeSandman)](https://github.com/ZeSandman)
|
||||
@@ -1,70 +1,3 @@
|
||||
## 1.5.1
|
||||
|
||||
### Enhancements
|
||||
|
||||
* **MapTool:** voeg links toe naar extensie naast export knop ([07d336d](https://github.com/elyukai/optolith-client/commit/07d336dbb3ceba9818185a9b8c0cdffa6283afb3))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MapTool:** zonder avatar kon je niet exporteren ([80fdffb](https://github.com/elyukai/optolith-client/commit/80fdffb373fccc467383cf6d98c1f07a92168ee4)), sluit [#1348](https://github.com/elyukai/optolith-client/issues/1348)
|
||||
* **MapTool:** export formaat voor MapTool versie 1.11.4 is aangepast ([9e9a113](https://github.com/elyukai/optolith-client/commit/9e9a1139d3821bb5a36fbaa973236319026d996a))
|
||||
* Voordeel *Inspireer Vertrouwen* kon gekocht worden met een actieve *incompetent* in een sociale vaardigheid ([8102bb1](https://github.com/elyukai/optolith-client/commit/8102bb1a9adac4849499380bf3bc2270aa7dd2c6)), sluit [#1351](https://github.com/elyukai/optolith-client/issues/1351)
|
||||
* Doorstreepte vereisten werden gerenderd met omsluitende tildes ([2c9332c](https://github.com/elyukai/optolith-client/commit/2c9332c37f9b2d8d9e0359a9e4dadf0a40beeba1)), sluit [#1349](https://github.com/elyukai/optolith-client/issues/1349)
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Features
|
||||
|
||||
* MapTool export ([87cec15](https://github.com/elyukai/optolith-client/commit/87cec15ba9751c329ee39719dc654895b9f4f193))
|
||||
* Portuguese translations ([d8c9f52](https://github.com/elyukai/optolith-client/commit/d8c9f52542120f9e5fa569728b3f11cb79102365), [#676](https://github.com/elyukai/optolith-client/issues/676))
|
||||
* Native Apple M1 support ([94e751f](https://github.com/elyukai/optolith-client/commit/94e751fda169c4de90cdb82346d3d864cf697300))
|
||||
|
||||
### Verbeteringen
|
||||
|
||||
* Security ([723e910](https://github.com/elyukai/optolith-client/commit/723e9106ebb178db6a7cccec2a022b3edaed5b98) and others)
|
||||
|
||||
### Probleemoplossingen
|
||||
|
||||
* Additional LE set a minimum required value of STR instead of CON ([390dcb6](https://github.com/elyukai/optolith-client/commit/390dcb616aaa68e5b103bb49dd75d1529c4e7e3a), [#787](https://github.com/elyukai/optolith-client/issues/787))
|
||||
* Two-handed (2H) and improvised (i) weapon indicators were missing on character sheet ([3f9fae3](https://github.com/elyukai/optolith-client/commit/3f9fae30a2291b8e53486a74dae8cc1533741b94), [#797](https://github.com/elyukai/optolith-client/issues/797))
|
||||
* Brawling weapons had no parry on character sheet ([a02700e](https://github.com/elyukai/optolith-client/commit/a02700eeccbd95a75f778d4d9e78ad7a2447dad8), [#1229](https://github.com/elyukai/optolith-client/issues/1229))
|
||||
* An entry that depends on a single activation of another entry disables removing of all activations of that entry if multiple activations are present ([092343f](https://github.com/elyukai/optolith-client/commit/092343f45da566a45b13b275899a458fed720a76), [#1097](https://github.com/elyukai/optolith-client/issues/1097))
|
||||
* Entries affecting spells to not be unfamiliar could be removed if Spell Enhancements were bought ([28f260a](https://github.com/elyukai/optolith-client/commit/28f260a2c4a96adb0d36585a478676f95c304414), [#827](https://github.com/elyukai/optolith-client/issues/827))
|
||||
* The equipment info area may have crashed ([ae2fbad](https://github.com/elyukai/optolith-client/commit/ae2fbad1c738354dd468c1b36b06eda729a879e5))
|
||||
* Professions that granted higher Combat Technique Ratings than what the selected Experience Level allowed were not filtered ([c8094f5](https://github.com/elyukai/optolith-client/commit/c8094f5ef15f62f69088386b9fc6c64f7d8bfd02), [#1244](https://github.com/elyukai/optolith-client/issues/1244))
|
||||
* INI/MOV modifiers on armor were handled as penalties ([eb5a068](https://github.com/elyukai/optolith-client/commit/eb5a06884db1894e1a9f2b4b040b114cb4dc85cb), [#1091](https://github.com/elyukai/optolith-client/issues/1091))
|
||||
* Language specializations did not always cost 1 AP and could not be activated multiple times for a single language ([0a59d9e](https://github.com/elyukai/optolith-client/commit/0a59d9e5e571e87525b18d0b25035e73f7cd87bf), [#1082](https://github.com/elyukai/optolith-client/issues/1082))
|
||||
* Portraits with transparency had a black background ([cc5ecd6](https://github.com/elyukai/optolith-client/commit/cc5ecd6f098ae6c080eeb3b306887727a99c1220), [#800](https://github.com/elyukai/optolith-client/issues/800))
|
||||
* Bornlander cultural package had Leatherworking instead of Prepare Food ([397abe2](https://github.com/elyukai/optolith-client/commit/397abe2b4a69582dc49422add1700807272c32d6), [#824](https://github.com/elyukai/optolith-client/issues/824))
|
||||
* Primary attribute changes for items were not applied in edit window, only on character sheet ([6c63ab1](https://github.com/elyukai/optolith-client/commit/6c63ab1b5051882ace71f15740c3a69fff4a267f), [#798](https://github.com/elyukai/optolith-client/issues/798))
|
||||
* Professions with due to EL impossible attribute values could be selected ([fdde329](https://github.com/elyukai/optolith-client/commit/fdde329de591319df3f6da11404dd7523d800c3e), [#1109](https://github.com/elyukai/optolith-client/issues/1109))
|
||||
* Summoning spellworks from professions did not register dependencies from prerequisites ([c1b2e26](https://github.com/elyukai/optolith-client/commit/c1b2e267cb3f02d2d18812415c39c58739ad99ec), [#1240](https://github.com/elyukai/optolith-client/issues/1240))
|
||||
* Typo in effect of Bliksemblind spell ([c58279c](https://github.com/elyukai/optolith-client/commit/c58279cc95aba5506824510d83aa6ffbc410107a), [#805](https://github.com/elyukai/optolith-client/issues/805))
|
||||
* Weight of Elves was not generated correctly ([3b05e69](https://github.com/elyukai/optolith-client/commit/3b05e69e27f7faa1634b50eb134000fe1a2c3a21), [#813](https://github.com/elyukai/optolith-client/issues/813))
|
||||
* Attributes on minimum value (8) did not allow a combat technique rating over 6 if the attribute was the combat technique's primary attribute ([47e66d6](https://github.com/elyukai/optolith-client/commit/47e66d65467999e2661eaac30a97b5a1ebfece94), [#835](https://github.com/elyukai/optolith-client/issues/835))
|
||||
* Too large PDF output when using a sheet background ([9cb2658](https://github.com/elyukai/optolith-client/commit/9cb26586f3a5026d06f41801d4bbf714631d06a4), [#1094](https://github.com/elyukai/optolith-client/issues/1094))
|
||||
* Auto-updater did not work in AppImage ([a24ff80](https://github.com/elyukai/optolith-client/commit/a24ff8082a5556fc07af05f113bacfd9c84428a4), [#753](https://github.com/elyukai/optolith-client/issues/753))
|
||||
* Some primary attribute prerequisites were not enforced ([ac0cdc3](https://github.com/elyukai/optolith-client/commit/ac0cdc377800a1eb380993dbb7914fa88b8f254e), [#1260](https://github.com/elyukai/optolith-client/issues/1260))
|
||||
* Range brackets input fields influenced other bracket fields ([0ab65e8](https://github.com/elyukai/optolith-client/commit/0ab65e869c26536127b386753db110f2e26b33af), [#1251](https://github.com/elyukai/optolith-client/issues/1251))
|
||||
* Requiring only one matching languages and script required all of them instead ([4dabbdb](https://github.com/elyukai/optolith-client/commit/4dabbdbcd23c1ffeff18a8b1f9497a4a8315aa6a), [#1343](https://github.com/elyukai/optolith-client/issues/1343))
|
||||
* Scripts did not require an active matching language ([1ed0c64](https://github.com/elyukai/optolith-client/commit/1ed0c64b0dd9873593169bd9994082d49299fa3e), [#1344](https://github.com/elyukai/optolith-client/issues/1344))
|
||||
* Some item properties were not saved ([f2e6138](https://github.com/elyukai/optolith-client/commit/f2e61386abe444aa76f0211469d50c30fde99a0c), [#1252](https://github.com/elyukai/optolith-client/issues/1252))
|
||||
* Unfamiliar spell selection in profession was optional ([3574d1b](https://github.com/elyukai/optolith-client/commit/3574d1ba46725d04a83286cd1055c080fe1de95f), [#1315](https://github.com/elyukai/optolith-client/issues/1315))
|
||||
* Combat techniques did not influence the minimum of their primary attributes ([1ce2db9](https://github.com/elyukai/optolith-client/commit/1ce2db9cfbb06b1ca8a549957b8225af71de9185), [#836](https://github.com/elyukai/optolith-client/issues/836))
|
||||
* Saving a hero cleared the undo history of multiple characters ([923b760](https://github.com/elyukai/optolith-client/commit/923b7605fee4c4288dc743e962728308f7254ca9), [#811](https://github.com/elyukai/optolith-client/issues/811))
|
||||
* Widened rows in second column of skills table on character sheet ([41ee35b](https://github.com/elyukai/optolith-client/commit/41ee35ba76af42ad837daa6c45a940406b0c5daa), [#1342](https://github.com/elyukai/optolith-client/issues/1342))
|
||||
|
||||
### Dank u
|
||||
|
||||
Ten laatste maar zeker niet ten minste een dikke **Dank u** voor iedereen die feedback en help geeft in de verschillende kanalen alsook de volgende mensen op GitHub die bijgedragen hebben tot deze release!
|
||||
|
||||
- [Elidia (@FloraMayr)](https://github.com/FloraMayr)
|
||||
- [JoveToo (@JoveToo)](https://github.com/JoveToo)
|
||||
- [Lector (@Lector)](https://github.com/Lector)
|
||||
- [Lorenz Cuno Klopfenstein (@LorenzCK)](https://github.com/LorenzCK)
|
||||
- [Marc Schwering (@m4rcsch)](https://github.com/m4rcsch)
|
||||
- [Pepelios (@Pepelios)](https://github.com/Pepelios)
|
||||
|
||||
## 1.4.2
|
||||
|
||||
### Verbeteringen
|
||||
@@ -48,6 +48,4 @@ Een lijst met relevante files:
|
||||
- `config.json` bevat de configuratie van Optolith. Het bevat geen kritieke data maar dingen zoals theme, taal (alleen als je een specifieke taal gekozen hebt) en zoverder.
|
||||
*Een noot voor Windows:* `AppData` is een *verborgen* folder. Om die te kunnen zien, open Windows Explorere, ga naar de "View" tab en activeer de setting "Hidden elements".
|
||||
|
||||
## Hoe schaal ik de UI voor Optolith in Linux?
|
||||
|
||||
Als je de `.tar.gz` hebt gedownload, ga je naar de uitgepakte folder en start je `GDK_DPI_SCALE=<Scale> ./Optolith` in je terminal, waarbij je `<scale>` moet vervangen met de gewenste schaal, e.g. `1.5`.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 1.5.0
|
||||
|
||||
Initial release.
|
||||
|
||||
### Features
|
||||
|
||||
### Enhancements
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
### Thank you
|
||||
|
||||
Last but definitely not least a big **Thank You** to the following people on GitHub who contributed to this release!
|
||||
|
||||
- [TBD (@tbd)](https://github.com/tbd)
|
||||
@@ -1,298 +0,0 @@
|
||||
// @ts-check
|
||||
import { FlatCompat } from "@eslint/eslintrc"
|
||||
import js from "@eslint/js"
|
||||
import eslintConfigPrettier from "eslint-config-prettier"
|
||||
import jsdoc from "eslint-plugin-jsdoc"
|
||||
import react from "eslint-plugin-react"
|
||||
import globals from "globals"
|
||||
import ts from "typescript-eslint"
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: import.meta.dirname,
|
||||
})
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
react.configs.flat.recommended,
|
||||
react.configs.flat['jsx-runtime'],
|
||||
...compat.extends("plugin:react-hooks/recommended"),
|
||||
.../** @type {import('eslint').Linter.Config[]} */ (ts.configs.recommended),
|
||||
jsdoc.configs["flat/recommended-typescript-error"],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.browser,
|
||||
},
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Possible problems
|
||||
"require-atomic-updates": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
|
||||
// Suggestions
|
||||
"accessor-pairs": "error",
|
||||
"arrow-body-style": "error",
|
||||
"class-methods-use-this": "warn",
|
||||
"default-case": "error",
|
||||
eqeqeq: "error",
|
||||
"guard-for-in": "error",
|
||||
"init-declarations": "error",
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": [
|
||||
2,
|
||||
{
|
||||
exceptions: ["Array", "Map", "Set"],
|
||||
},
|
||||
],
|
||||
"no-extra-bind": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "off",
|
||||
"no-return-assign": "error",
|
||||
"no-script-url": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "error",
|
||||
"object-shorthand": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-destructuring": [
|
||||
2,
|
||||
{
|
||||
VariableDeclarator: {
|
||||
array: true,
|
||||
object: true,
|
||||
},
|
||||
AssignmentExpression: {
|
||||
array: false,
|
||||
object: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
"prefer-named-capture-group": "warn",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-object-has-own": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
radix: "error",
|
||||
"require-unicode-regexp": "error",
|
||||
"symbol-description": "error",
|
||||
yoda: "error",
|
||||
|
||||
// TypeScript
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
2,
|
||||
{
|
||||
multiline: {
|
||||
delimiter: "none",
|
||||
},
|
||||
singleline: {
|
||||
delimiter: "semi",
|
||||
requireLast: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
selector: "interface",
|
||||
format: ["PascalCase"],
|
||||
custom: {
|
||||
regex: "^I[A-Z]",
|
||||
match: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-inferrable-types": "error",
|
||||
"@typescript-eslint/no-redeclare": "error",
|
||||
"@typescript-eslint/no-shadow": [
|
||||
"error",
|
||||
{
|
||||
ignoreTypeValueShadow: true,
|
||||
}
|
||||
],
|
||||
"no-unused-expressions": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "error",
|
||||
"@typescript-eslint/no-use-before-define": "error",
|
||||
"@typescript-eslint/no-useless-constructor": "error",
|
||||
"@typescript-eslint/prefer-for-of": "error",
|
||||
"@typescript-eslint/prefer-readonly": "error",
|
||||
"@typescript-eslint/require-array-sort-compare": "error",
|
||||
"@typescript-eslint/strict-boolean-expressions": "error",
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
|
||||
// React
|
||||
"react/boolean-prop-naming": "error",
|
||||
"react/destructuring-assignment": "error",
|
||||
"react/no-access-state-in-setstate": "error",
|
||||
"react/no-array-index-key": "error",
|
||||
"react/no-children-prop": "error",
|
||||
"react/no-danger": "error",
|
||||
"react/no-danger-with-children": "error",
|
||||
"react/no-deprecated": "error",
|
||||
"react/no-did-mount-set-state": "error",
|
||||
"react/no-did-update-set-state": "warn",
|
||||
"react/no-direct-mutation-state": "error",
|
||||
"react/no-find-dom-node": "error",
|
||||
"react/no-is-mounted": "error",
|
||||
"react/no-redundant-should-component-update": "error",
|
||||
"react/no-render-return-value": "error",
|
||||
"react/no-typos": "error",
|
||||
"react/no-string-refs": "error",
|
||||
"react/no-this-in-sfc": "error",
|
||||
"react/no-unescaped-entities": "error",
|
||||
"react/no-unsafe": "error",
|
||||
"react/no-unused-prop-types": "error",
|
||||
"react/no-unused-state": "error",
|
||||
"react/no-will-update-set-state": "error",
|
||||
"react/prefer-es6-class": "error",
|
||||
"react/prefer-stateless-function": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/require-render-return": "error",
|
||||
"react/self-closing-comp": "error",
|
||||
"react/state-in-constructor": [2, "never"],
|
||||
"react/style-prop-object": "error",
|
||||
"react/void-dom-elements-no-children": "error",
|
||||
"react/jsx-boolean-value": [2, "never"],
|
||||
"react/jsx-child-element-spacing": "error",
|
||||
"react/jsx-closing-tag-location": "error",
|
||||
"react/jsx-curly-newline": "error",
|
||||
"react/jsx-curly-spacing": "error",
|
||||
"react/jsx-equals-spacing": "error",
|
||||
"react/jsx-first-prop-new-line": "error",
|
||||
"react/jsx-indent-props": [2, 2],
|
||||
"react/jsx-no-bind": "error",
|
||||
"react/jsx-no-comment-textnodes": "error",
|
||||
"react/jsx-no-literals": "error",
|
||||
"react/jsx-no-target-blank": "error",
|
||||
"react/jsx-one-expression-per-line": [
|
||||
2,
|
||||
{
|
||||
allow: "single-child",
|
||||
},
|
||||
],
|
||||
"react/jsx-curly-brace-presence": [
|
||||
2,
|
||||
{
|
||||
props: "never",
|
||||
children: "ignore",
|
||||
},
|
||||
],
|
||||
"react/jsx-fragments": "error",
|
||||
"react/jsx-pascal-case": "error",
|
||||
"react/jsx-props-no-multi-spaces": "error",
|
||||
"react/jsx-props-no-spreading": "error",
|
||||
"react/jsx-tag-spacing": "error",
|
||||
"react/jsx-wrap-multilines": [
|
||||
2,
|
||||
{
|
||||
declaration: "parens-new-line",
|
||||
assignment: "parens-new-line",
|
||||
return: "parens-new-line",
|
||||
arrow: "parens-new-line",
|
||||
condition: "parens-new-line",
|
||||
logical: "parens-new-line",
|
||||
prop: "ignore",
|
||||
},
|
||||
],
|
||||
|
||||
// React Hooks
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
|
||||
// JSDoc
|
||||
"jsdoc/check-tag-names": [
|
||||
"error",
|
||||
{
|
||||
definedTags: ["main", "integer", "minItems"],
|
||||
},
|
||||
],
|
||||
"jsdoc/require-jsdoc": [
|
||||
"error",
|
||||
{
|
||||
contexts: [
|
||||
"TSInterfaceDeclaration",
|
||||
"TSMethodSignature",
|
||||
"TSEnumDeclaration",
|
||||
"TSTypeAliasDeclaration",
|
||||
"ExportNamedDeclaration > VariableDeclaration",
|
||||
],
|
||||
publicOnly: true,
|
||||
require: {
|
||||
ArrowFunctionExpression: true,
|
||||
ClassDeclaration: true,
|
||||
ClassExpression: true,
|
||||
FunctionDeclaration: true,
|
||||
FunctionExpression: true,
|
||||
MethodDefinition: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"jsdoc/require-param": "off",
|
||||
"jsdoc/require-returns": "off",
|
||||
"jsdoc/require-description": [
|
||||
"error",
|
||||
{
|
||||
contexts: [
|
||||
"any",
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
{
|
||||
ignores: [
|
||||
".webpack/**",
|
||||
"*.config.js",
|
||||
"deploy/**",
|
||||
"src/App/**",
|
||||
"src/Control/**",
|
||||
"src/Data/**",
|
||||
"src/Debug/**",
|
||||
"src/System/**",
|
||||
"src/database/contents/**",
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {import('@jest/types').Config.InitialOptions}
|
||||
*/
|
||||
module.exports = {
|
||||
testRegex: "(/__tests__/.*|(\\.|/)(test|spec)|_(test|spec)\\.bs)\\.jsx?$",
|
||||
moduleFileExtensions: [
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
],
|
||||
testEnvironment: "node"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// @ts-check
|
||||
import { join } from "node:path"
|
||||
import { jsonSchema } from "optolith-tsjsonschemamd/renderers"
|
||||
|
||||
/** @type {import("optolith-tsjsonschemamd").GeneratorOptions} */
|
||||
export default {
|
||||
sourceDir: join("src", "shared", "schema"),
|
||||
outputs: [
|
||||
{
|
||||
targetDir: join("src", "assets", "schema"),
|
||||
renderer: jsonSchema({ spec: "Draft_07" }),
|
||||
}
|
||||
],
|
||||
clean: true,
|
||||
verbose: false
|
||||
}
|
||||
Generated
+15661
-20214
File diff suppressed because it is too large
Load Diff
+79
-70
@@ -1,90 +1,99 @@
|
||||
{
|
||||
"name": "optolith",
|
||||
"productName": "Optolith",
|
||||
"version": "2.0.0-alpha.1",
|
||||
"productName": "Optolith Insider",
|
||||
"version": "1.4.3-alpha.0",
|
||||
"description": "A generator for heroes for the P&P-RPG 'The Dark Eye'.",
|
||||
"author": {
|
||||
"name": "Lukas Obermann",
|
||||
"email": "support@optolith.app"
|
||||
},
|
||||
"homepage": "https://optolith.app",
|
||||
"homepage": "https://github.com/elyukai/optolith-client",
|
||||
"license": "MPL-2.0",
|
||||
"main": ".webpack/main.cjs",
|
||||
"main": "src/Main.bs.js",
|
||||
"dependencies": {
|
||||
"@optolith/entity-descriptions": "^0.2.1",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"adm-zip": "^0.5.16",
|
||||
"debug": "^4.3.7",
|
||||
"electron-updater": "^6.3.9",
|
||||
"electron-window-state": "5.0.3",
|
||||
"immer": "^10.1.1",
|
||||
"moment": "^2.30.1",
|
||||
"mousetrap": "1.6.5",
|
||||
"optolith-character-schema": "^0.0.3",
|
||||
"optolith-database-schema": "^0.17.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"semver": "^7.5.4"
|
||||
"@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": {
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@rspack/cli": "^1.0.8",
|
||||
"@rspack/core": "^1.0.8",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/react": "^18.3.11",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/ssh2-sftp-client": "^9.0.4",
|
||||
"css-loader": "^7.1.2",
|
||||
"electron": "^32.1.2",
|
||||
"electron-builder": "^25.1.7",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-jsdoc": "^50.3.1",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-tsdoc": "^0.3.0",
|
||||
"glob": "^11.0.0",
|
||||
"globals": "^15.10.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"optolith-tsjsonschemamd": "^0.10.7",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"sass": "^1.79.4",
|
||||
"sass-loader": "^16.0.2",
|
||||
"ssh2-sftp-client": "^11.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.2",
|
||||
"typescript-eslint": "^8.8.0"
|
||||
"@commitlint/cli": "^12.1.1",
|
||||
"@commitlint/config-conventional": "^12.1.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/mousetrap": "^1.6.7",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-custom-scrollbars": "^4.0.7",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/react-portal": "^4.0.2",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/semver": "^7.3.4",
|
||||
"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.32.11",
|
||||
"standard-version": "^9.2.0",
|
||||
"webpack": "^5.35.1",
|
||||
"webpack-cli": "^4.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prerelease:win": "set NODE_ENV=development && node ./build/build.js --win --prerelease",
|
||||
"prerelease:linux": "NODE_ENV=development node -r dotenv/config ./build/build.js --linux --prerelease",
|
||||
"prerelease:mac": "NODE_ENV=development node ./build/build.js --mac --prerelease",
|
||||
"stable:win": "set NODE_ENV=production && node ./build/build.js --win --stable",
|
||||
"stable:linux": "NODE_ENV=production node ./build/build.js --linux --stable",
|
||||
"stable:mac": "NODE_ENV=production node ./build/build.js --mac --stable",
|
||||
"js:build": "rspack build",
|
||||
"js:watch": "rspack build -w",
|
||||
"prerelease:win": "set NODE_ENV=development && node ./deploy/buildInsiderWin.js",
|
||||
"prerelease:linux": "NODE_ENV=development node -r dotenv/config ./deploy/buildInsiderLinux.js",
|
||||
"prerelease:mac": "NODE_ENV=development node ./deploy/buildInsiderMac.js",
|
||||
"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 dist/main.css",
|
||||
"css:watch": "sass --watch --style=compressed src/Main.scss dist/main.css",
|
||||
"build": "npm run re:build && npm run css:build && npm run js:build",
|
||||
"js:build": "webpack --config ./webpack.config.js",
|
||||
"js:watch": "webpack -w --config ./webpack.config.js",
|
||||
"ts:build": "tsc",
|
||||
"ts:watch": "tsc -w",
|
||||
"createschemas": "otjsmd",
|
||||
"lint": "eslint \"src/**\"",
|
||||
"start": "electron --js-flags='--stack-size=5000' .",
|
||||
"test": "glob -c \"node --import tsx --test\" \"./src/shared/**/*.test.ts\"",
|
||||
"re:build": "bsb -make-world",
|
||||
"re:watch": "bsb -make-world -w",
|
||||
"re:clean": "bsb -clean-world",
|
||||
"lint": "eslint \"src/\" --ext \".ts\" --ext \".tsx\"",
|
||||
"start": "electron .",
|
||||
"test": "jest",
|
||||
"circ": "madge -c ./src",
|
||||
"release:main": "standard-version --skip.commit --skip.tag",
|
||||
"release:pre": "standard-version --prerelease=alpha",
|
||||
"buildicns": "iconutil -c icns src/assets/icon/AppIcon.iconset && iconutil -c icns src/assets/icon/AppIcon.pre.iconset"
|
||||
"release:pre": "standard-version --prerelease=alpha --skip.changelog"
|
||||
},
|
||||
"repository": "github:elyukai/optolith-client",
|
||||
"type": "module"
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
import { resolve } from "node:path"
|
||||
import { getOptimization } from "./rspack.optimization.js"
|
||||
import { rendererPlugins } from "./rspack.plugins.js"
|
||||
import { rules } from "./rspack.rules.js"
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {EnvironmentMode} mode
|
||||
* @returns {import("@rspack/core").Configuration[]}
|
||||
*/
|
||||
const createRendererConfig = (name, mode) => [
|
||||
{
|
||||
name: `renderer_${name}`,
|
||||
mode,
|
||||
entry: {
|
||||
[`renderer_${name}`]: `./src/${name}_window/index.tsx`,
|
||||
},
|
||||
target: "electron-renderer",
|
||||
optimization: getOptimization(`renderer_${name}`),
|
||||
module: {
|
||||
rules: rules,
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
conditionNames: ["browser", "default", "import", "require"],
|
||||
},
|
||||
output: {
|
||||
path: resolve(import.meta.dirname, ".webpack"),
|
||||
filename: "[name].js",
|
||||
globalObject: "globalThis",
|
||||
},
|
||||
plugins: rendererPlugins(mode),
|
||||
externals: {
|
||||
"electron/renderer": "commonjs electron/renderer",
|
||||
"electron/common": "commonjs electron/common",
|
||||
},
|
||||
externalsPresets: {
|
||||
electronRenderer: true,
|
||||
},
|
||||
profile: true,
|
||||
devtool: "source-map",
|
||||
},
|
||||
{
|
||||
name: `renderer_${name}_preload`,
|
||||
mode,
|
||||
entry: {
|
||||
[`renderer_${name}_preload`]: `./src/${name}_window_preload/index.ts`,
|
||||
},
|
||||
target: "electron-preload",
|
||||
module: {
|
||||
rules: rules,
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
},
|
||||
output: {
|
||||
path: resolve(import.meta.dirname, ".webpack"),
|
||||
filename: "[name].cjs",
|
||||
},
|
||||
externals: {
|
||||
"electron/renderer": "commonjs electron/renderer",
|
||||
"electron/common": "commonjs electron/common",
|
||||
},
|
||||
externalsPresets: {
|
||||
electronPreload: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* @typedef {"development" | "production"} EnvironmentMode
|
||||
* @returns {import("@rspack/core").Configuration[]}
|
||||
*/
|
||||
export default () => {
|
||||
/** @type {EnvironmentMode} */
|
||||
const mode = process.env.NODE_ENV === "production" ? "production" : "development"
|
||||
|
||||
return [
|
||||
{
|
||||
name: "main",
|
||||
mode,
|
||||
entry: {
|
||||
main: "./src/main/index.ts",
|
||||
},
|
||||
target: "electron-main",
|
||||
optimization: getOptimization("main"),
|
||||
module: {
|
||||
rules: rules,
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
},
|
||||
output: {
|
||||
path: resolve(import.meta.dirname, ".webpack"),
|
||||
filename: "[name].cjs",
|
||||
},
|
||||
externals: {
|
||||
"electron/main": "commonjs electron/main",
|
||||
"electron/common": "commonjs electron/common",
|
||||
},
|
||||
externalsPresets: {
|
||||
electronMain: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "database",
|
||||
mode,
|
||||
entry: {
|
||||
database: "./src/database/index.ts",
|
||||
},
|
||||
target: "electron-main",
|
||||
optimization: getOptimization("database"),
|
||||
module: {
|
||||
rules: rules,
|
||||
},
|
||||
resolve: {
|
||||
symlinks: false,
|
||||
},
|
||||
output: {
|
||||
path: resolve(import.meta.dirname, ".webpack"),
|
||||
filename: "[name].cjs",
|
||||
},
|
||||
externalsPresets: {
|
||||
electronMain: true,
|
||||
},
|
||||
externals: {
|
||||
"optolith-database-schema": "module optolith-database-schema",
|
||||
},
|
||||
},
|
||||
...createRendererConfig("main", mode),
|
||||
...createRendererConfig("updater", mode),
|
||||
...createRendererConfig("settings", mode),
|
||||
]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {string} processType
|
||||
* @returns {import("@rspack/core").Configuration["optimization"]}
|
||||
*/
|
||||
export const getOptimization = (processType) => ({
|
||||
splitChunks: {
|
||||
chunks: "all",
|
||||
cacheGroups: {
|
||||
default: {
|
||||
name: processType,
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
},
|
||||
defaultVendors: {
|
||||
name: `${processType}_vendors`,
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1,20 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
import rspack from "@rspack/core"
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin"
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("./rspack.config.js").EnvironmentMode} mode
|
||||
* @returns {import("@rspack/core").Configuration["plugins"]}
|
||||
*/
|
||||
export const rendererPlugins = (mode) => [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "[name].html",
|
||||
template: "./src/template.html",
|
||||
}),
|
||||
new rspack.CssExtractRspackPlugin({
|
||||
filename: `assets/css/${mode === "development" ? "[name].css" : "[name].[hash].css"}`,
|
||||
chunkFilename: `assets/css/${mode === "development" ? "[id].css" : "[id].[hash].css"}`,
|
||||
}),
|
||||
]
|
||||
@@ -1,73 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
import rspack from "@rspack/core"
|
||||
|
||||
/**
|
||||
* @type {Required<import("@rspack/core").ModuleOptions>["rules"]}
|
||||
*/
|
||||
export const rules = [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: [/node_modules/],
|
||||
loader: "builtin:swc-loader",
|
||||
resolve: {
|
||||
extensionAlias: {
|
||||
'.js': ['.tsx', '.ts', 'jsx', '.js'],
|
||||
},
|
||||
},
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
throwIfNamespace: true,
|
||||
useBuiltins: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'javascript/auto',
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|svg)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "assets/images/[name].[hash][ext][query]",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(icns|ico)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "assets/icons/[name].[hash][ext][query]",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|otf|woff2?)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "assets/fonts/[name].[hash][ext][query]",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(css|sass|scss)$/,
|
||||
use: [
|
||||
rspack.CssExtractRspackPlugin.loader,
|
||||
"css-loader",
|
||||
"resolve-url-loader",
|
||||
{
|
||||
loader: "sass-loader",
|
||||
options: {
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
generator: {
|
||||
filename: "assets/css/[name].[hash][ext][query]",
|
||||
},
|
||||
type: "javascript/auto",
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,120 @@
|
||||
type action =
|
||||
| SetLocale(result(string, string))
|
||||
| SwitchActiveItemHints
|
||||
| SetTheme(FilterOptions.theme)
|
||||
| SwitchEditingHeroAfterCreationPhase
|
||||
| SwitchAnimations
|
||||
| SetTab(FilterOptions.tab)
|
||||
| SetHeroListSortOrder(FilterOptions.heroListSortOptions)
|
||||
| SetHeroListVisibilityFilter(FilterOptions.heroListVisibilityFilter)
|
||||
| SetHeroListFilterText(string)
|
||||
| CreateHero(Hero.t)
|
||||
| OpenHero(int)
|
||||
| SaveHero(int)
|
||||
| DeleteHero(int)
|
||||
| DuplicateHero(int)
|
||||
| UndoHeroAction
|
||||
| RedoHeroAction
|
||||
| SetHeroName(string)
|
||||
| SetCustomProfessionName(string)
|
||||
| SetFamily(string)
|
||||
| SetPlaceOfBirth(string)
|
||||
| SetDateOfBirth(string)
|
||||
| SetAge(string)
|
||||
| SetHairColor(int)
|
||||
| SetEyeColor(int)
|
||||
| SetSize(string)
|
||||
| SetWeight(string)
|
||||
| SetTitle(string)
|
||||
| SetSocialStatus(int)
|
||||
| SetCharacteristics(string)
|
||||
| SetOtherInfo(string)
|
||||
| SetCultureAreaKnowledge(string)
|
||||
| AddAdventurePoints(int)
|
||||
| EndHeroCreation
|
||||
| SetPactCategory(option(int))
|
||||
| SetPactLevel(int)
|
||||
| SetPactTargetType(int)
|
||||
| SetTargetDomain(int)
|
||||
| SetCustomTargetDomain(string)
|
||||
| SetTargetName(string)
|
||||
| SetRace(int)
|
||||
| SetRaceVariant(int)
|
||||
| SetRacesSortOrder(FilterOptions.racesSortOptions)
|
||||
| SetRacesFilterText(string)
|
||||
| SetCulture(int)
|
||||
| SetCulturesSortOrder(FilterOptions.culturesSortOptions)
|
||||
| SetCulturesVisibilityFilter(FilterOptions.culturesVisibilityFilter)
|
||||
| SetCulturesFilterText(string)
|
||||
| SetProfession(int)
|
||||
| SetProfessionVariant(int)
|
||||
| SetProfessionsSortOrder(FilterOptions.professionsSortOptions)
|
||||
| SetProfessionsVisibilityFilter(FilterOptions.professionsVisibilityFilter)
|
||||
| SetProfessionsGroupVisibilityFilter(
|
||||
FilterOptions.professionsGroupVisibilityFilter,
|
||||
)
|
||||
| SetProfessionsFilterText(string)
|
||||
| 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)
|
||||
| AddCombatTechniquePoint(int)
|
||||
| RemoveCombatTechniquePoint(int)
|
||||
| SetCombatTechniquesSortOrder(FilterOptions.combatTechniquesSortOptions)
|
||||
| SetCombatTechniquesFilterText(string)
|
||||
| AddLiturgicalChant(int)
|
||||
| RemoveLiturgicalChant(int)
|
||||
| AddLiturgicalChantPoint(int)
|
||||
| RemoveLiturgicalChantPoint(int)
|
||||
| AddBlessing(int)
|
||||
| RemoveBlessing(int)
|
||||
| SetLiturgicalChantsSortOrder(FilterOptions.liturgicalChantsSortOptions)
|
||||
| SetActiveLiturgicalChantsFilterText(string)
|
||||
| SetInactiveLiturgicalChantsFilterText(string)
|
||||
| SetPetName(string)
|
||||
| SetPetSize(string)
|
||||
| SetPetType(string)
|
||||
| SetPetSpentAp(string)
|
||||
| SetPetTotalAp(string)
|
||||
| SetPetCourage(string)
|
||||
| SetPetSagacity(string)
|
||||
| SetPetIntuition(string)
|
||||
| SetPetCharisma(string)
|
||||
| SetPetDexterity(string)
|
||||
| SetPetAgility(string)
|
||||
| SetPetConstitution(string)
|
||||
| SetPetStrength(string)
|
||||
| SetPetLp(string)
|
||||
| SetPetAe(string)
|
||||
| SetPetSpi(string)
|
||||
| SetPetTou(string)
|
||||
| SetPetPro(string)
|
||||
| SetPetIni(string)
|
||||
| SetPetMov(string)
|
||||
| SetPetAttack(string)
|
||||
| SetPetAt(string)
|
||||
| SetPetPa(string)
|
||||
| SetPetDp(string)
|
||||
| SetPetReach(string)
|
||||
| SetPetActions(string)
|
||||
| SetPetSkills(string)
|
||||
| SetPetAbilities(string)
|
||||
| SetPetNotes(string);
|
||||
@@ -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,
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user