Compare commits

..
Author SHA1 Message Date
Lukas Obermann c8fac07d76 unstaged id changes 2023-01-17 20:16:52 +01:00
elyukai 593eb4dba7 refactor: finished Profession dynamic entry and style 2021-01-07 23:29:34 +01:00
elyukai b1373e6aaf refactor: converted Profession 2021-01-07 16:52:55 +01:00
elyukai accc5a4ea5 refactor: converted and adjusted Culture 2021-01-07 02:21:19 +01:00
elyukai 3eb61cc55c refactor: worked on dependencies and warnings in OCaml 2021-01-07 02:01:05 +01:00
elyukai f7e5e06ff2 refactor: parser almost done 2021-01-05 03:05:06 +01:00
elyukai 35048e1032 refactor: created all special ability entities 2021-01-04 22:12:38 +01:00
elyukai 9ee4626f49 refactor: finished new select option conversion 2021-01-04 18:45:11 +01:00
elyukai 4f698af39b refactor: more conversions to OCaml 2021-01-02 02:48:35 +01:00
elyukai 7e055864a4 refactor: started refactoring Prerequisite 2021-01-01 18:45:45 +01:00
elyukai 669e2cb1b0 refactor: some more conversions to OCaml 2020-12-31 01:14:25 +01:00
elyukai b6971d190b refactor: further OCaml conversions 2020-12-29 21:57:09 +01:00
elyukai b0f1869886 feat: added OCaml and started converting to new schema 2020-12-29 21:31:43 +01:00
elyukai 74f7811f18 build: exclude BuckleScript output from Git 2020-12-27 16:55:15 +01:00
elyukai 66a5329331 build: remove BuckleScript output from Git 2020-12-27 16:54:10 +01:00
Lukas Obermann dfe9e37ae9 test: started rewriting some old tests for modules 2020-11-26 02:03:42 +01:00
Lukas Obermann c41d3c4912 refactor: active Activatable AP calculation done 2020-11-25 20:04:42 +01:00
Lukas Obermann 956cf3958d refactor: extract activatable type into own module 2020-11-25 12:41:59 +01:00
Lukas Obermann 1e31e7b8de refactor: basic init progress visualization 2020-11-24 02:28:25 +01:00
Lukas Obermann 6776e9582a refactor: main process
This completes the implementation of main process features. It features
basic window handling, data initialization and adjustments of decoders
to match the database and it's schemes.
A very rough renderer process script has been added to avoid depedencies
with errors to be used as well as to be able to test ipc.
2020-11-24 01:42:00 +01:00
Lukas Obermann e7e02c025c refactor: started writing worker thread bindings 2020-11-20 23:52:37 +01:00
Lukas Obermann e0b8625a4b refactor: converted app config to ReasonML 2020-11-20 22:54:02 +01:00
Lukas Obermann 7ee370c687 refactor: converted main script to ReasonML 2020-11-20 01:06:41 +01:00
Lukas Obermann ced296b47f refactor: use plain variants instead of pairs 2020-11-17 01:49:35 +01:00
Lukas Obermann 832da43ecd build: adjusted incoming config
For the rework large parts of the folder structure were changed and this
needs to be reflected in new build tooling configuration.
2020-11-14 01:41:14 +01:00
Lukas Obermann a62195f390 build: applied development changes to database 2020-11-13 16:56:42 +01:00
Lukas Obermann 4da9621b3e build: upgraded Reason output 2020-11-13 15:43:12 +01:00
Lukas Obermann e15bf1784c Merge branch 'develop' into feature/ama3 2020-11-13 15:04:07 +01:00
Lukas Obermann be7e174353 Adjusted parser to fit database schema fixes 2020-10-08 16:01:26 +02:00
Lukas Obermann 91f6ccbdaa Synced parser with schemes except for item schemes 2020-10-07 01:17:43 +02:00
Lukas Obermann 6bee4968cf Changed structure for missing modules 2020-10-05 17:11:53 +02:00
Lukas Obermann 51b2fd85fe Some decoder restructuring 2020-10-05 15:06:19 +02:00
Lukas Obermann 785366f25a Added explanation for dynamic and static 2020-10-05 12:12:51 +02:00
Lukas Obermann 2e488573f0 Finished basic decoding docs 2020-10-05 12:06:43 +02:00
Lukas Obermann a91241b759 Started adding docs for decoder 2020-10-05 04:29:25 +02:00
Lukas Obermann 13e90fe2ca Hide language order implementation 2020-10-05 03:36:44 +02:00
Lukas Obermann 0da83cb257 Removed app folder and integrated content into src and docs folder 2020-10-05 01:29:38 +02:00
Lukas Obermann 0fadd70dbd Moved database to src folder 2020-10-05 01:23:21 +02:00
Lukas Obermann cbcebc8722 Basic rework of parser done
Compiler without errors
2020-10-05 01:06:49 +02:00
Lukas Obermann 5b2be2aba6 Some data modules simplifications
Reworked decoder for select options
2020-10-04 01:18:35 +02:00
Lukas Obermann fb26257254 Reworked prerequisites 2020-10-02 02:43:56 +02:00
Lukas Obermann 6141671c11 Converted all skills and some other entries to new schema 2020-10-01 16:01:10 +02:00
Lukas Obermann 8983c129be Started reworking JSON schema 2020-10-01 01:29:08 +02:00
Lukas Obermann f0fedb2ee5 Refactores Attributes and Skills 2020-09-30 01:32:50 +02:00
Lukas Obermann a8cc0883df Switched database branch 2020-09-29 22:04:43 +02:00
Lukas Obermann 0820860125 Merge branch 'develop' into feature/ama3 2020-09-29 22:04:11 +02:00
Lukas Obermann 635c11cda2 Finished id rework 2020-07-20 02:25:58 +02:00
Lukas Obermann 57032d78bc Started reworking ids 2020-07-20 00:28:01 +02:00
Lukas Obermann a89fd07252 Started adding AP spent difference function 2020-07-15 11:31:01 +02:00
Lukas Obermann f2380eb9b4 Finished single activatable active entry generation 2020-07-14 01:45:37 +02:00
Lukas Obermann 3348c08808 Finished active validation 2020-07-14 01:39:50 +02:00
Lukas Obermann 4492304173 Finished single inactive entry generation and validation 2020-07-12 20:22:53 +02:00
Lukas Obermann 9acffd878e Finished inactive validation functions for validation details 2020-07-12 13:43:36 +02:00
Lukas Obermann 1667683ceb Stricter compiler config
Started adding inactive validation
2020-07-12 01:51:18 +02:00
Lukas Obermann ae44828365 Started working on full inactive activatable generation 2020-07-10 00:26:11 +02:00
Lukas Obermann 3f48a8f42d Finished function to get available select options for inactive activatable entry 2020-07-08 22:09:46 +02:00
Lukas Obermann 5d341bb6b6 Removed genType as it's not needed currently 2020-07-08 15:11:26 +02:00
Lukas Obermann d4f15baf96 Restructures folders for Reason files 2020-07-08 15:07:23 +02:00
Lukas Obermann bfd1f05c89 Updated Reason libraries
Started converting function to get valid select options for inactive A/D/SA to Reason
2020-07-07 00:22:49 +02:00
Lukas Obermann de582f9140 Merged source ref grouping and availability checks 2020-07-01 15:41:57 +02:00
Lukas Obermann f79c84caeb Added transducers and source ref groups 2020-06-25 01:05:47 +02:00
Lukas Obermann 521bca34fe Splitted Activatable module into separate files 2020-06-15 23:15:54 +02:00
elyukai d616157397 Finished Extended Style conversion 2020-06-15 01:31:17 +02:00
elyukai b8cd0794ca Added more documentation to public functions
Started converting ExtendedStyleUtils
Removed more converted TS code
2020-06-14 01:03:38 +02:00
elyukai 7ec14231e0 Finished Dependencies module 2020-06-13 20:34:28 +02:00
elyukai 053fcb2874 Finished Prerequisites module
Started working on Dependencies
Removed some TS code that has been converted to Reason
2020-06-12 23:40:24 +02:00
elyukai 9b7948f6f6 Started converting prerequisite validators 2020-06-12 00:46:51 +02:00
elyukai d31be41a12 Started converting prerequisite functions to Reason 2020-06-12 00:31:34 +02:00
Lukas Obermann 117a732594 Started to convert actions 2020-05-30 00:39:27 +02:00
Lukas Obermann 6fe2c3323f Reworked models to fit Reason generated types 2020-05-28 23:20:21 +02:00
Lukas Obermann b888851995 Finished Dropdown 2020-05-25 21:36:36 +02:00
Lukas Obermann 9b95187936 Merge branch 'develop' into feature/ama3 2020-05-25 15:06:13 +02:00
Lukas Obermann 283825d6d9 Added Switch and ScrollView and started to add Dropdowns in Reason 2020-05-25 11:59:44 +02:00
Lukas Obermann 8da9b6f641 Added ReactUtils for easier reuse of commonly used patterns 2020-05-21 13:41:40 +02:00
Lukas Obermann 2b20c8598a Added some more UI views 2020-05-21 00:34:17 +02:00
Lukas Obermann 58df332c06 Started to add some universal React components 2020-05-19 02:07:13 +02:00
Lukas Obermann b1f9a155f9 Synced TS data structures with RE data structures
Started reworking TS files to match the TS data structure definitions
There are still A LOT of errors due to this
2020-05-12 01:02:39 +02:00
Lukas Obermann 2bc235292c Merge part 2 2020-05-09 22:10:02 +02:00
Lukas Obermann 1eae92cabc Merge branch 'develop' into feature/ama3 2020-05-09 22:07:12 +02:00
Lukas Obermann df6c9c310e Merge branch 'develop' into feature/ama3 2020-03-06 01:49:51 +01:00
Lukas Obermann e653063350 Started trying to convert YAML parser to Reason 2020-03-04 15:38:13 +01:00
1666 changed files with 94893 additions and 94355 deletions
+3
View File
@@ -0,0 +1,3 @@
src/App/Utilities/YAML/Schema/
*.js
*.gen.tsx
+484
View File
@@ -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
-13
View File
@@ -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']
+33
View File
@@ -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.
-73
View File
@@ -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
+5 -15
View File
@@ -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.
+11 -20
View File
@@ -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. 133150 (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.
+10
View File
@@ -0,0 +1,10 @@
---
name: Question
about: I have a question.
title: ''
labels: Question
assignees: ''
---
-43
View File
@@ -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
+37 -52
View File
@@ -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 }}
+37 -52
View File
@@ -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 }}
+51
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,3 @@
[submodule "app/Database"]
path = src/database/contents
path = src/Database
url = https://github.com/elyukai/optolith-data.git
+24
View File
@@ -0,0 +1,24 @@
<component name="libraryTable">
<library name="Bucklescript" type="javaScript">
<properties>
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
<item url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
<item url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
<item url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
<item url="file://$PROJECT_DIR$/node_modules/reason-react" />
<item url="file://$PROJECT_DIR$/node_modules/reductive" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-jest" />
<root url="file://$PROJECT_DIR$/node_modules/@glennsl/bs-json" />
<root url="file://$PROJECT_DIR$/node_modules/bs-fetch" />
<root url="file://$PROJECT_DIR$/node_modules/bs-webapi" />
<root url="file://$PROJECT_DIR$/node_modules/reason-react" />
<root url="file://$PROJECT_DIR$/node_modules/reductive" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
Generated
+1
View File
@@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/Database" vcs="Git" />
</component>
</project>
+2
View File
@@ -0,0 +1,2 @@
break-infix-before-func = true
break-collection-expressions = wrap
-5
View File
@@ -1,5 +0,0 @@
arrowParens: avoid
printWidth: 100
semi: false
tabWidth: 2
trailingComma: all
+1 -1
View File
@@ -3,7 +3,7 @@
"configurations": [
{
"name": "Electron: Debug Main",
"type": "pwa-node",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
+6 -9
View File
@@ -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,11 @@
"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.sandbox": {
"kind": "opam",
"switch": "${workspaceFolder:optolith-client-rework}"
}
}
+12 -48
View File
@@ -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
View File
@@ -1 +1 @@
Lukas Obermann <lukas.obermann@icloud.com>
Lukas Obermann <lukas.obermann@outlook.de>
-277
View File
@@ -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
View File
@@ -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 &ndash; 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** &mdash; <https://git-scm.com/downloads> or via a package manager
- **Node.js** with **npm** &mdash; <https://nodejs.org/en/> or via [`nvm`](https://github.com/nvm-sh/nvm)
- *Optional:* **Visual Studio Code** &mdash; <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
```
&hellip;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
+1 -10
View File
@@ -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:
Submodule app/Database deleted from d17be0b48b
-129
View File
@@ -1,129 +0,0 @@
## 1.5.1
### Miglioramenti
* **MapTool:** aggiunto collegamento allestensione 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 lesportazione ([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 didentità ([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 didentità ([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 sullequipaggiamento ([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 dEsperienza 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 didentità ([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 dEsperienza) 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 didentità ([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 didentità. [#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 nellinserimento 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&nbsp;4* invece di *Alchimia&nbsp;4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
- Campo di testo delle abilità degli animali nel foglio didentità è troppo piccolo per contenere tutte le abilità. [#733](https://github.com/elyukai/optolith-client/issues/733)
- Labilità 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 licona di caricamento invece del ritratto su fogli didentità 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)
-16
View File
@@ -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.
+63
View File
@@ -0,0 +1,63 @@
{
"name": "optolith-client",
"version": "0.1.0",
"sources": [
{ "dir": "deploy" },
{ "dir": "src" },
{ "dir": "src/Activatable" },
{ "dir": "src/Activatable/SpecialAbilities" },
{ "dir": "src/App/Actions" },
{ "dir": "src/App/Constants" },
{ "dir": "src/App/Containers" },
{ "dir": "src/App/Models" },
{ "dir": "src/App/Reducers" },
{ "dir": "src/App/Selectors" },
{ "dir": "src/App/Utilities" },
{ "dir": "src/App/Utilities/__tests__", "type": "dev" },
{ "dir": "src/App/Utilities/YAML" },
{ "dir": "src/App/Views" },
{ "dir": "src/App/Views/Universal" },
{ "dir": "src/Cloud" },
{ "dir": "src/Control" },
{ "dir": "src/Data" },
{ "dir": "src/Data/__tests__", "type": "dev" },
{ "dir": "src/Debug" },
{ "dir": "src/Equipment" },
{ "dir": "src/ExternalBindings" },
{ "dir": "src/Increasable" },
{ "dir": "src/Increasable/__tests__", "type": "dev" },
{ "dir": "src/Increasable/MagicalActions" },
{ "dir": "src/Init" },
{ "dir": "src/Misc" },
{ "dir": "src/Misc/__tests__", "type": "dev" },
{ "dir": "src/Prerequisites" },
{ "dir": "src/RaceCultureProfession" },
{ "dir": "src/Rules" },
{ "dir": "src/Sources" },
{ "dir": "src/Utilities" },
{ "dir": "src/Utilities/__tests__", "type": "dev" }
],
"package-specs": {
"module": "commonjs",
"in-source": true
},
"suffix": ".bs.js",
"bs-dependencies": [
"@glennsl/bs-json",
"@glennsl/bs-jest",
"bs-fetch",
"bs-webapi",
"decco",
"reason-react",
"reductive"
],
"ppx-flags": [ "decco/ppx" ],
"reason": {
"react-jsx": 3
},
"warnings": {
"number": "+A-42-48",
"error": "+A-3-4-27-32-41-44-102"
},
"namespace": true
}
-124
View File
@@ -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:
"© 2017present 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.`)
-10
View File
@@ -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>
-58
View File
@@ -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.`,
)
}
}
}
+25
View File
@@ -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 */
+361
View File
@@ -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();
// });
// };
// };
+73
View File
@@ -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,
}
+24
View File
@@ -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 () })
+74
View File
@@ -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
View File
@@ -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>
+29
View File
@@ -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
}
+32
View File
@@ -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"
}
}
}
-51
View File
@@ -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}`)
}
+113
View File
@@ -0,0 +1,113 @@
# Decoding
Here you'll find some useful information about the design of the YAML decoders.
## Overview
Decoding the database is splitted into parsing the YAML files and decoding the result. Parsing is done by the `Yaml_Parse` module and the `Yaml_Decode` module controls the decoding of the various entry types. The `Yaml` module then combines all functions into a single exported function.
## Decode Entry Types
### Basic decoder structure
All decodable types share a common decoder layout. All values only relevant while decoding are inside a `Decode` module at the same level as the type it is the decoder for.
```re
// Since this is the actual type, it's not really part of the decoding pipeline
// but I'll keep it here to make the definitions complete.
type t = {
// ...
};
module Decode = {
module Translation = {
type t = {
// ... (language-specific properties)
};
let t = Json.Decode.{
// ... (decode language-specific properties)
};
}
module TranslationMap = TranslationMap.Make(Translation);
type multilingual = {
// ... (language-independent properties)
translations: TranslationMap.t,
}
let multilingual = json =>
Json.Decode.{
// ... (decode language-independent properties)
translations: json |> field("translations", TranslationMap.Decode.t),
};
let resolveTranslations = (langs, x) =>
Ley_Option.Infix.(
x.translations
|> TranslationMap.Decode.getFromLanguageOrder(langs)
<&> (translation => {
// ... (merge language-independent and language-specific properties)
})
);
let t = (langs, json) =>
json |> multilingual |> resolveTranslations(langs);
};
```
### The two steps of decoding
All *static types* (i.e., all data defined in YAML files) share a common structure concerning their decode process.
#### 1. Decode a multilingual entry
Since language-specific values in the YAML files are always defined in dictionaries, we always split the language-specific and the language-independent parts.
The language-specific part of a type is always defined in a separate module `Translation` as record `t`. The `Translation` module is used for the functor `TranslationMap.Make`, which creates functions to decode the dictionaries and to resolve them later (see next step).
The language-independent part of a type is defined as record `multilingual` and always has the property `translations`, which is the `TranslationMap` type created by the `TranslationMap.Make` functor.
Now we can decode all language-independent values and the `TranslationMap`. Using the `Decode.t` function on the `TranslationMap`, it is not completely decoded, but instead only which languages are available, which is important because we need to check whether this entry is available in one of the selected languages. This then almost mirrors the structure of the data in the YAML files.
The corresponding function is called `multilingual`.
#### 2. Resolve translation maps with language settings
In the second steps, the translations maps are reduced to one language to form the main type together with the language-independent values.
By making use of the `Decode.getFromLanguageOrder` function on the translation map, we can get the values in the most preferred language and decode them at once. Since it returns an `Option`, we can safely continue only if a matching language has been found. Then we can build the main record based on the decoded language-specific part and the language-independent part.
The corresponding function is called `resolveTranslations`.
Together, `multilingual` and `resolveTranslations` form the `t` decoder function for the main type.
### Main types
In order to be used easier, an `assoc` auxiliary decoder function should be created to return a pair of the entry's id as well as the entry itself, since then it is easier to build a map of them. Some nested types use that, too.
### Nested types
Some types have nested types that have own translations (like professions and their variants). In those cases, you basically take the same steps mentioned above, although you can skip creating a main `t` decoder since that can't be used.
Instead, the main type's `multilingual` type references the nested type's `multilingual` and the `multilingual` decoder uses the nested type's `multilingual` decoder.
In step 2, it is basically the same: `resolveTranslations` of the nested type is called in `resolveTranslations` of the main type.
### Visibility conventions
For main types, only the main type itself and it's `t` (or `assoc`) function should be exposed. For nested types in the same module (as submodules), you can hide all decode types and functions. For nested types in a different module, you'll need to expose the `multilingual` and `resolveTranslations` functions and thus the `multilingual` type, which should always be abstract, since it should never be changed outside of the module.
As a rule of thumb: Just export what is *really* needed and also always check if a decode type can be made abstract.
### Naming conventions
- The language-specific module is always named `Translation` and contains a type `t` with a corresponding decoder `t` (the latter two are a requirement for the `TranslationMap.Make` functor anyway).
- The built translation map module is called `TranslationMap`.
- The record containing all language-independent values as well as the translation map is called `multilingual` and the translation map is at the property `translations` (just like in the source).
- A decoder for a specific type has the same name as the type.
- The function resolving the translation map and returning the actual main type is called `resolveTranslations`.
- The function combining `multilingual` and `resolveTranslations` is called `t`, since it decodes the type `t`.
You can, of course, always create additional helper functions for `multilingual`, `resolveTranslations` and `decode` if your type is usually in a container, then you need to provide a proper suffix for the function as well, so you basically get, for example, `multilingual` and `multilingualList` (if your type is in a list). Exception: If your type is `t`, just use the suffix instead, for example, `t` and `assoc` (if your type is used in a map) then.
+8
View File
@@ -0,0 +1,8 @@
# *Dynamic* and *Static*
In entry type definition files, you'll often see the content is wrapped in a `Dynamic` and a `Static` module. What does that mean?
- **Static** values come from the YAML files, so they have been defined there and they will not change while the program runs.
- **Dynamic** values are kind of like *instances* of the *static* values. They are part of the heroes and define how an entry is configured for that hero.
For example, a `Spell.Static.t` type is the static representation of a spell: It contains it's name, IC, duration, target category, and so on. The `Spell.Dynamic.t` type is the dynamic representation of the spell for a specific hero: Is the spell active? If yes, which SR does it have?
@@ -1,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
+1 -5
View File
@@ -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
+1 -5
View File
@@ -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
-20
View File
@@ -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
```
+58
View File
@@ -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 didentità. [#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 nellinserimento 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&nbsp;4* invece di *Alchimia&nbsp;4*. [#568](https://github.com/elyukai/optolith-client/issues/568)
- Campo di testo delle abilità degli animali nel foglio didentità è troppo piccolo per contenere tutte le abilità. [#733](https://github.com/elyukai/optolith-client/issues/733)
- Labilità 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 licona di caricamento invece del ritratto su fogli didentità 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
+1 -3
View File
@@ -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`.
+15
View File
@@ -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)
-298
View File
@@ -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/**",
],
},
]
+25
View File
@@ -0,0 +1,25 @@
// @ts-check
/**
* @typedef {import('ts-jest')}
* @type {import('@jest/types').Config.InitialOptions}
*/
module.exports = {
transform: {
"^.+\\.tsx?$": "ts-jest"
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(j|t)sx?$",
moduleFileExtensions: [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
testEnvironment: "node",
globals: {
"ts-jest": {
tsConfig: "tsconfig.test.json"
}
}
}
-16
View File
@@ -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
}
+17538 -20602
View File
File diff suppressed because it is too large Load Diff
+92 -69
View File
@@ -1,90 +1,113 @@
{
"name": "optolith",
"productName": "Optolith",
"version": "2.0.0-alpha.1",
"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": "dist/main.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",
"ajv": "^6.12.6",
"bs-fetch": "^0.6.2",
"bs-platform": "^9.0.2",
"bs-webapi": "^0.19.1",
"decco": "^1.4.0",
"electron-devtools-installer": "^3.1.1",
"electron-log": "^4.2.4",
"electron-updater": "^4.3.1",
"electron-window-state": "^5.0.3",
"moment": "^2.25.3",
"mousetrap": "^1.6.5",
"react": "^17.0.2",
"react-custom-scrollbars": "^4.2.1",
"react-dom": "^17.0.2",
"react-markdown": "^4.3.1",
"react-portal": "^4.2.1",
"react-redux": "^7.2.1",
"react-textfit": "^1.1.0",
"reason-react": "^0.9.1",
"reductive": "^2.1.1",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"relude": "^0.63.1",
"remark-breaks": "^1.0.5",
"reselect": "^4.0.0",
"semver": "^7.3.2",
"ssh2-sftp-client": "^5.3.1",
"tslib": "^1.11.2",
"yaml": "^1.10.0"
},
"devDependencies": {
"@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",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"custom-env": "2.0.1",
"dotenv": "8.2.0",
"electron": "^10.4.3",
"electron-builder": "^22.10.5",
"electron-notarize": "^1.0.0",
"eslint": "^7.25.0",
"eslint-plugin-react": "^7.23.2",
"eslint-plugin-react-hooks": "^4.2.0",
"fs-extra": "9.1.0",
"gentype": "^3.48.0",
"html-webpack-plugin": "^5.3.1",
"husky": "^4.3.0",
"jest": "^26.6.3",
"qusly-core": "2.5.2",
"sass": "^1.32.11",
"standard-version": "^9.2.0",
"ts-jest": "^26.5.5",
"ts-loader": "^9.1.1",
"typescript": "3.8.3",
"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"
}
}
}
-136
View File
@@ -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),
]
}
-24
View File
@@ -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,
},
},
},
})
-20
View File
@@ -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"}`,
}),
]
-73
View File
@@ -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",
},
]
+120
View File
@@ -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);
+199
View File
@@ -0,0 +1,199 @@
type action =
| AddAttributePoint(int)
| RemoveAttributePoint(int)
| AddLifePoint
| RemoveLifePoint
| AddArcaneEnergyPoint
| RemoveArcaneEnergyPoint
| AddKarmaPoint
| RemoveKarmaPoint
| AddLostLifePoint
| RemoveLostLifePoint
| AddLostLifePoints(int)
| AddBoughtBackArcaneEnergyPoint
| RemoveBoughtBackArcaneEnergyPoint
| AddLostArcaneEnergyPoint
| RemoveLostArcaneEnergyPoint
| AddLostArcaneEnergyPoints(int)
| AddBoughtBackKarmaPoint
| RemoveBoughtBackKarmaPoint
| AddLostKarmaPoint
| RemoveLostKarmaPoint
| AddLostKarmaPoints(int)
| SetAttributeAdjustment(int);
// export const addAttributePoint = (id: string): ReduxAction<Promise<void>> =>
// async (dispatch, getState) => {
// const state = getState ()
// const wiki_attributes = getWikiAttributes (state)
// const mhero = getCurrentHeroPresent (state)
// const mhero_attributes = fmapF (mhero) (HeroModel.A.attributes)
//
// const missingAPForInc =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// liftM2 (getAreSufficientAPAvailableForIncrease (getIsInCharacterCreation (state))
// (bind (mhero_attributes)
// (lookup (id))))
// (lookup (id) (wiki_attributes)),
// join
// )
//
// if (isNothing (missingAPForInc)) {
// dispatch<AddAttributePointAction> ({
// type: ActionTypes.ADD_ATTRIBUTE_POINT,
// payload: {
// id,
// },
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAPForInc)))
// }
// }
//
// export const addLifePoint: ReduxAction<Promise<void>> =
// async (dispatch, getState) => {
// const state = getState ()
// const mhero = getCurrentHeroPresent (state)
//
// const missingAP =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// liftM2 (pipe (
//
// // get AP for added points
// curryN (getAPForInc) ("D"),
//
// // AP are passed to result and result finally gets the available AP
// getMissingAP (getIsInCharacterCreation (state))
// ))
// (getAddedLifePoints (state)),
// join
// )
//
// if (isNothing (missingAP)) {
// dispatch<AddLifePointAction> ({
// type: ActionTypes.ADD_LIFE_POINT,
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
// }
// }
//
// export const addArcaneEnergyPoint: ReduxAction<Promise<void>> =
// async (dispatch, getState) => {
// const state = getState ()
// const mhero = getCurrentHeroPresent (state)
//
// const missingAP =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// liftM2 (pipe (
//
// // get AP for added points
// curryN (getAPForInc) ("D"),
//
// // AP are passed to result and result finally gets the available AP
// getMissingAP (getIsInCharacterCreation (state))
// ))
// (getAddedArcaneEnergyPoints (state)),
// join
// )
//
// if (isNothing (missingAP)) {
// dispatch<AddArcaneEnergyPointAction> ({
// type: ActionTypes.ADD_ARCANE_ENERGY_POINT,
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
// }
// }
//
// export const addKarmaPoint: ReduxAction<Promise<void>> =
// async (dispatch, getState) => {
// const state = getState ()
// const mhero = getCurrentHeroPresent (state)
//
// const missingAP =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// liftM2 (pipe (
//
// // get AP for added points
// curryN (getAPForInc) ("D"),
//
// // AP are passed to result and result finally gets the available AP
// getMissingAP (getIsInCharacterCreation (state))
// ))
// (getAddedKarmaPoints (state)),
// join
// )
//
// if (isNothing (missingAP)) {
// dispatch<AddKarmaPointAction> ({
// type: ActionTypes.ADD_KARMA_POINT,
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
// }
// }
//
// export const addBoughtBackAEPoint: ReduxAction<Promise<void>> =
// async (dispatch, getState) => {
// const state = getState ()
// const mhero = getCurrentHeroPresent (state)
//
// const missingAP =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// bindF (getMissingAP (getIsInCharacterCreation (state))
// (2))
// )
//
// if (isNothing (missingAP)) {
// dispatch<AddBoughtBackAEPointAction> ({
// type: ActionTypes.ADD_BOUGHT_BACK_AE_POINT,
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
// }
// }
//
// export const addBoughtBackKPPoint: ReduxAction<Promise<void>> =
// async (dispatch, getState) => {
// const state = getState ()
// const mhero = getCurrentHeroPresent (state)
//
// const missingAP =
// pipe_ (
// mhero,
// bindF (hero => getAvailableAPMap (HeroModel.A.id (hero)) (state, { hero })),
// join,
// bindF (getMissingAP (getIsInCharacterCreation (state))
// (2))
// )
//
// if (isNothing (missingAP)) {
// dispatch<AddBoughtBackKPPointAction> ({
// type: ActionTypes.ADD_BOUGHT_BACK_KP_POINT,
// })
// }
// else {
// await dispatch (addNotEnoughAPAlert (fromJust (missingAP)))
// }
// }
+5
View File
@@ -0,0 +1,5 @@
type action =
| SetCulture(int)
| SetCulturesSortOrder(FilterOptions.culturesSortOptions)
| SetCulturesVisibilityFilter(FilterOptions.culturesVisibilityFilter)
| SetCulturesFilterText(string);
+50
View File
@@ -0,0 +1,50 @@
type t =
| Advantage of Advantage.Static.t
| Disadvantage of Disadvantage.Static.t
| GeneralSpecialAbility of GeneralSpecialAbility.Static.t
| FatePointSpecialAbility of FatePointSpecialAbility.Static.t
| CombatSpecialAbility of CombatSpecialAbility.Static.t
| MagicalSpecialAbility of MagicalSpecialAbility.Static.t
| StaffEnchantment of StaffEnchantment.Static.t
| FamiliarSpecialAbility of FamiliarSpecialAbility.Static.t
| KarmaSpecialAbility of KarmaSpecialAbility.Static.t
| ProtectiveWardingCircleSpecialAbility of
ProtectiveWardingCircleSpecialAbility.Static.t
| CombatStyleSpecialAbility of CombatStyleSpecialAbility.Static.t
| AdvancedCombatSpecialAbility of AdvancedCombatSpecialAbility.Static.t
| CommandSpecialAbility of CommandSpecialAbility.Static.t
| MagicStyleSpecialAbility of MagicStyleSpecialAbility.Static.t
| AdvancedMagicalSpecialAbility of AdvancedMagicalSpecialAbility.Static.t
| SpellSwordEnchantment of SpellSwordEnchantment.Static.t
| DaggerRitual of DaggerRitual.Static.t
| InstrumentEnchantment of InstrumentEnchantment.Static.t
| AttireEnchantment of AttireEnchantment.Static.t
| OrbEnchantment of OrbEnchantment.Static.t
| WandEnchantment of WandEnchantment.Static.t
| BrawlingSpecialAbility of BrawlingSpecialAbility.Static.t
| AncestorGlyph of AncestorGlyph.Static.t
| CeremonialItemSpecialAbility of CeremonialItemSpecialAbility.Static.t
| Sermon of Sermon.Static.t
| LiturgicalStyleSpecialAbility of LiturgicalStyleSpecialAbility.Static.t
| AdvancedKarmaSpecialAbility of AdvancedKarmaSpecialAbility.Static.t
| Vision of Vision.Static.t
| MagicalTradition of MagicalTradition.Static.t
| BlessedTradition of BlessedTradition.Static.t
| Paktgeschenk of Paktgeschenk.Static.t
| SikaryanRaubSonderfertigkeit of SikaryanRaubSonderfertigkeit.Static.t
| LykanthropischeGabe of LykanthropischeGabe.Static.t
| Talentstilsonderfertigkeit of Talentstilsonderfertigkeit.Static.t
| ErweiterteTalentsonderfertigkeit of
ErweiterteTalentsonderfertigkeit.Static.t
| Kugelzauber of Kugelzauber.Static.t
| Kesselzauber of Kesselzauber.Static.t
| Kappenzauber of Kappenzauber.Static.t
| Spielzeugzauber of Spielzeugzauber.Static.t
| Schalenzauber of Schalenzauber.Static.t
| SexSchicksalspunkteSonderfertigkeit of
SexSchicksalspunkteSonderfertigkeit.Static.t
| SexSonderfertigkeit of SexSonderfertigkeit.Static.t
| Waffenzauber of Waffenzauber.Static.t
| Sichelritual of Sichelritual.Static.t
| Ringzauber of Ringzauber.Static.t
| Chronikzauber of Chronikzauber.Static.t
+349
View File
@@ -0,0 +1,349 @@
let isActive (x : 'a Activatable_Dynamic.t) = Ley_List.Extra.notNull x.active
let isActiveM x = Ley_Option.option false isActive x
let id = function
| Activatable.Advantage x -> Id.Activatable.Advantage x.id
| Disadvantage x -> Disadvantage x.id
| GeneralSpecialAbility x -> GeneralSpecialAbility x.id
| FatePointSpecialAbility x -> FatePointSpecialAbility x.id
| CombatSpecialAbility x -> CombatSpecialAbility x.id
| MagicalSpecialAbility x -> MagicalSpecialAbility x.id
| StaffEnchantment x -> StaffEnchantment x.id
| FamiliarSpecialAbility x -> FamiliarSpecialAbility x.id
| KarmaSpecialAbility x -> KarmaSpecialAbility x.id
| ProtectiveWardingCircleSpecialAbility x ->
ProtectiveWardingCircleSpecialAbility x.id
| CombatStyleSpecialAbility x -> CombatStyleSpecialAbility x.id
| AdvancedCombatSpecialAbility x -> AdvancedCombatSpecialAbility x.id
| CommandSpecialAbility x -> CommandSpecialAbility x.id
| MagicStyleSpecialAbility x -> MagicStyleSpecialAbility x.id
| AdvancedMagicalSpecialAbility x -> AdvancedMagicalSpecialAbility x.id
| SpellSwordEnchantment x -> SpellSwordEnchantment x.id
| DaggerRitual x -> DaggerRitual x.id
| InstrumentEnchantment x -> InstrumentEnchantment x.id
| AttireEnchantment x -> AttireEnchantment x.id
| OrbEnchantment x -> OrbEnchantment x.id
| WandEnchantment x -> WandEnchantment x.id
| BrawlingSpecialAbility x -> BrawlingSpecialAbility x.id
| AncestorGlyph x -> AncestorGlyph x.id
| CeremonialItemSpecialAbility x -> CeremonialItemSpecialAbility x.id
| Sermon x -> Sermon x.id
| LiturgicalStyleSpecialAbility x -> LiturgicalStyleSpecialAbility x.id
| AdvancedKarmaSpecialAbility x -> AdvancedKarmaSpecialAbility x.id
| Vision x -> Vision x.id
| MagicalTradition x -> MagicalTradition x.id
| BlessedTradition x -> BlessedTradition x.id
| Paktgeschenk x -> Paktgeschenk x.id
| SikaryanRaubSonderfertigkeit x -> SikaryanRaubSonderfertigkeit x.id
| LykanthropischeGabe x -> LykanthropischeGabe x.id
| Talentstilsonderfertigkeit x -> Talentstilsonderfertigkeit x.id
| ErweiterteTalentsonderfertigkeit x -> ErweiterteTalentsonderfertigkeit x.id
| Kugelzauber x -> Kugelzauber x.id
| Kesselzauber x -> Kesselzauber x.id
| Kappenzauber x -> Kappenzauber x.id
| Spielzeugzauber x -> Spielzeugzauber x.id
| Schalenzauber x -> Schalenzauber x.id
| SexSchicksalspunkteSonderfertigkeit x ->
SexSchicksalspunkteSonderfertigkeit x.id
| SexSonderfertigkeit x -> SexSonderfertigkeit x.id
| Waffenzauber x -> Waffenzauber x.id
| Sichelritual x -> Sichelritual x.id
| Ringzauber x -> Ringzauber x.id
| Chronikzauber x -> Chronikzauber x.id
let id' = function
| Activatable.Advantage x -> x.id
| Disadvantage x -> x.id
| GeneralSpecialAbility x -> x.id
| FatePointSpecialAbility x -> x.id
| CombatSpecialAbility x -> x.id
| MagicalSpecialAbility x -> x.id
| StaffEnchantment x -> x.id
| FamiliarSpecialAbility x -> x.id
| KarmaSpecialAbility x -> x.id
| ProtectiveWardingCircleSpecialAbility x -> x.id
| CombatStyleSpecialAbility x -> x.id
| AdvancedCombatSpecialAbility x -> x.id
| CommandSpecialAbility x -> x.id
| MagicStyleSpecialAbility x -> x.id
| AdvancedMagicalSpecialAbility x -> x.id
| SpellSwordEnchantment x -> x.id
| DaggerRitual x -> x.id
| InstrumentEnchantment x -> x.id
| AttireEnchantment x -> x.id
| OrbEnchantment x -> x.id
| WandEnchantment x -> x.id
| BrawlingSpecialAbility x -> x.id
| AncestorGlyph x -> x.id
| CeremonialItemSpecialAbility x -> x.id
| Sermon x -> x.id
| LiturgicalStyleSpecialAbility x -> x.id
| AdvancedKarmaSpecialAbility x -> x.id
| Vision x -> x.id
| MagicalTradition x -> x.id
| BlessedTradition x -> x.id
| Paktgeschenk x -> x.id
| SikaryanRaubSonderfertigkeit x -> x.id
| LykanthropischeGabe x -> x.id
| Talentstilsonderfertigkeit x -> x.id
| ErweiterteTalentsonderfertigkeit x -> x.id
| Kugelzauber x -> x.id
| Kesselzauber x -> x.id
| Kappenzauber x -> x.id
| Spielzeugzauber x -> x.id
| Schalenzauber x -> x.id
| SexSchicksalspunkteSonderfertigkeit x -> x.id
| SexSonderfertigkeit x -> x.id
| Waffenzauber x -> x.id
| Sichelritual x -> x.id
| Ringzauber x -> x.id
| Chronikzauber x -> x.id
let idDeepVariant x = x |> id |> Id.Activatable.to_nested
let name = function
| Activatable.Advantage x -> x.name
| Disadvantage x -> x.name
| GeneralSpecialAbility x -> x.name
| FatePointSpecialAbility x -> x.name
| CombatSpecialAbility x -> x.name
| MagicalSpecialAbility x -> x.name
| StaffEnchantment x -> x.name
| FamiliarSpecialAbility x -> x.name
| KarmaSpecialAbility x -> x.name
| ProtectiveWardingCircleSpecialAbility x -> x.name
| CombatStyleSpecialAbility x -> x.name
| AdvancedCombatSpecialAbility x -> x.name
| CommandSpecialAbility x -> x.name
| MagicStyleSpecialAbility x -> x.name
| AdvancedMagicalSpecialAbility x -> x.name
| SpellSwordEnchantment x -> x.name
| DaggerRitual x -> x.name
| InstrumentEnchantment x -> x.name
| AttireEnchantment x -> x.name
| OrbEnchantment x -> x.name
| WandEnchantment x -> x.name
| BrawlingSpecialAbility x -> x.name
| AncestorGlyph x -> x.name
| CeremonialItemSpecialAbility x -> x.name
| Sermon x -> x.name
| LiturgicalStyleSpecialAbility x -> x.name
| AdvancedKarmaSpecialAbility x -> x.name
| Vision x -> x.name
| MagicalTradition x -> x.name
| BlessedTradition x -> x.name
| Paktgeschenk x -> x.name
| SikaryanRaubSonderfertigkeit x -> x.name
| LykanthropischeGabe x -> x.name
| Talentstilsonderfertigkeit x -> x.name
| ErweiterteTalentsonderfertigkeit x -> x.name
| Kugelzauber x -> x.name
| Kesselzauber x -> x.name
| Kappenzauber x -> x.name
| Spielzeugzauber x -> x.name
| Schalenzauber x -> x.name
| SexSchicksalspunkteSonderfertigkeit x -> x.name
| SexSonderfertigkeit x -> x.name
| Waffenzauber x -> x.name
| Sichelritual x -> x.name
| Ringzauber x -> x.name
| Chronikzauber x -> x.name
let selectOptions = function
| Activatable.Advantage x -> x.selectOptions
| Disadvantage x -> x.selectOptions
| GeneralSpecialAbility x -> x.selectOptions
| FatePointSpecialAbility x -> x.selectOptions
| CombatSpecialAbility x -> x.selectOptions
| MagicalSpecialAbility x -> x.selectOptions
| StaffEnchantment x -> x.selectOptions
| FamiliarSpecialAbility x -> x.selectOptions
| KarmaSpecialAbility x -> x.selectOptions
| ProtectiveWardingCircleSpecialAbility x -> x.selectOptions
| CombatStyleSpecialAbility x -> x.selectOptions
| AdvancedCombatSpecialAbility x -> x.selectOptions
| CommandSpecialAbility x -> x.selectOptions
| MagicStyleSpecialAbility x -> x.selectOptions
| AdvancedMagicalSpecialAbility x -> x.selectOptions
| SpellSwordEnchantment x -> x.selectOptions
| DaggerRitual x -> x.selectOptions
| InstrumentEnchantment x -> x.selectOptions
| AttireEnchantment x -> x.selectOptions
| OrbEnchantment x -> x.selectOptions
| WandEnchantment x -> x.selectOptions
| BrawlingSpecialAbility x -> x.selectOptions
| AncestorGlyph x -> x.selectOptions
| CeremonialItemSpecialAbility x -> x.selectOptions
| Sermon x -> x.selectOptions
| LiturgicalStyleSpecialAbility x -> x.selectOptions
| AdvancedKarmaSpecialAbility x -> x.selectOptions
| Vision x -> x.selectOptions
| MagicalTradition x -> x.selectOptions
| BlessedTradition x -> x.selectOptions
| Paktgeschenk x -> x.selectOptions
| SikaryanRaubSonderfertigkeit x -> x.selectOptions
| LykanthropischeGabe x -> x.selectOptions
| Talentstilsonderfertigkeit x -> x.selectOptions
| ErweiterteTalentsonderfertigkeit x -> x.selectOptions
| Kugelzauber x -> x.selectOptions
| Kesselzauber x -> x.selectOptions
| Kappenzauber x -> x.selectOptions
| Spielzeugzauber x -> x.selectOptions
| Schalenzauber x -> x.selectOptions
| SexSchicksalspunkteSonderfertigkeit x -> x.selectOptions
| SexSonderfertigkeit x -> x.selectOptions
| Waffenzauber x -> x.selectOptions
| Sichelritual x -> x.selectOptions
| Ringzauber x -> x.selectOptions
| Chronikzauber x -> x.selectOptions
let input = function
| Activatable.Advantage x -> x.input
| Disadvantage x -> x.input
| GeneralSpecialAbility x -> x.input
| FatePointSpecialAbility x -> x.input
| CombatSpecialAbility x -> x.input
| MagicalSpecialAbility x -> x.input
| StaffEnchantment x -> x.input
| FamiliarSpecialAbility x -> x.input
| KarmaSpecialAbility x -> x.input
| ProtectiveWardingCircleSpecialAbility _ -> None
| CombatStyleSpecialAbility x -> x.input
| AdvancedCombatSpecialAbility x -> x.input
| CommandSpecialAbility x -> x.input
| MagicStyleSpecialAbility x -> x.input
| AdvancedMagicalSpecialAbility x -> x.input
| SpellSwordEnchantment x -> x.input
| DaggerRitual x -> x.input
| InstrumentEnchantment x -> x.input
| AttireEnchantment x -> x.input
| OrbEnchantment x -> x.input
| WandEnchantment x -> x.input
| BrawlingSpecialAbility x -> x.input
| AncestorGlyph x -> x.input
| CeremonialItemSpecialAbility x -> x.input
| Sermon x -> x.input
| LiturgicalStyleSpecialAbility x -> x.input
| AdvancedKarmaSpecialAbility x -> x.input
| Vision x -> x.input
| MagicalTradition x -> x.input
| BlessedTradition x -> x.input
| Paktgeschenk x -> x.input
| SikaryanRaubSonderfertigkeit x -> x.input
| LykanthropischeGabe x -> x.input
| Talentstilsonderfertigkeit x -> x.input
| ErweiterteTalentsonderfertigkeit x -> x.input
| Kugelzauber x -> x.input
| Kesselzauber x -> x.input
| Kappenzauber x -> x.input
| Spielzeugzauber x -> x.input
| Schalenzauber x -> x.input
| SexSchicksalspunkteSonderfertigkeit x -> x.input
| SexSonderfertigkeit x -> x.input
| Waffenzauber x -> x.input
| Sichelritual x -> x.input
| Ringzauber x -> x.input
| Chronikzauber x -> x.input
let apValue = function
| Activatable.Advantage x -> x.apValue
| Disadvantage x -> x.apValue
| GeneralSpecialAbility x -> x.apValue
| FatePointSpecialAbility x -> x.apValue
| CombatSpecialAbility x -> x.apValue
| MagicalSpecialAbility x -> x.apValue
| StaffEnchantment x -> x.apValue
| FamiliarSpecialAbility x -> x.apValue
| KarmaSpecialAbility x -> x.apValue
| ProtectiveWardingCircleSpecialAbility x -> x.apValue
| CombatStyleSpecialAbility x -> x.apValue
| AdvancedCombatSpecialAbility x -> x.apValue
| CommandSpecialAbility x -> x.apValue
| MagicStyleSpecialAbility x -> x.apValue
| AdvancedMagicalSpecialAbility x -> x.apValue
| SpellSwordEnchantment x -> x.apValue
| DaggerRitual x -> x.apValue
| InstrumentEnchantment x -> x.apValue
| AttireEnchantment x -> x.apValue
| OrbEnchantment x -> x.apValue
| WandEnchantment x -> x.apValue
| BrawlingSpecialAbility x -> x.apValue
| AncestorGlyph x -> x.apValue
| CeremonialItemSpecialAbility x -> x.apValue
| Sermon x -> x.apValue
| LiturgicalStyleSpecialAbility x -> x.apValue
| AdvancedKarmaSpecialAbility x -> x.apValue
| Vision x -> x.apValue
| MagicalTradition x -> x.apValue
| BlessedTradition x -> x.apValue
| Paktgeschenk x -> x.apValue
| SikaryanRaubSonderfertigkeit x -> x.apValue
| LykanthropischeGabe x -> x.apValue
| Talentstilsonderfertigkeit x -> x.apValue
| ErweiterteTalentsonderfertigkeit x -> x.apValue
| Kugelzauber x -> x.apValue
| Kesselzauber x -> x.apValue
| Kappenzauber x -> x.apValue
| Spielzeugzauber x -> x.apValue
| Schalenzauber x -> x.apValue
| SexSchicksalspunkteSonderfertigkeit x -> x.apValue
| SexSonderfertigkeit x -> x.apValue
| Waffenzauber x -> x.apValue
| Sichelritual x -> x.apValue
| Ringzauber x -> x.apValue
| Chronikzauber x -> x.apValue
let apValue' x =
x |> apValue |> function
| Some (Activatable_Shared.ApValue.Flat x) -> Some (OneOrMany.One x)
| Some (PerLevel xs) -> Some (Many xs)
| Some Option | None -> None
let max = function
| Activatable.Advantage x -> x.max
| Disadvantage x -> x.max
| GeneralSpecialAbility x -> x.max
| FatePointSpecialAbility x -> x.max
| CombatSpecialAbility x -> x.max
| MagicalSpecialAbility x -> x.max
| StaffEnchantment x -> x.max
| FamiliarSpecialAbility x -> x.max
| KarmaSpecialAbility x -> x.max
| ProtectiveWardingCircleSpecialAbility x -> x.max
| CombatStyleSpecialAbility x -> x.max
| AdvancedCombatSpecialAbility x -> x.max
| CommandSpecialAbility x -> x.max
| MagicStyleSpecialAbility x -> x.max
| AdvancedMagicalSpecialAbility x -> x.max
| SpellSwordEnchantment x -> x.max
| DaggerRitual x -> x.max
| InstrumentEnchantment x -> x.max
| AttireEnchantment x -> x.max
| OrbEnchantment x -> x.max
| WandEnchantment x -> x.max
| BrawlingSpecialAbility x -> x.max
| AncestorGlyph x -> x.max
| CeremonialItemSpecialAbility x -> x.max
| Sermon x -> x.max
| LiturgicalStyleSpecialAbility x -> x.max
| AdvancedKarmaSpecialAbility x -> x.max
| Vision x -> x.max
| MagicalTradition x -> x.max
| BlessedTradition x -> x.max
| Paktgeschenk x -> x.max
| SikaryanRaubSonderfertigkeit x -> x.max
| LykanthropischeGabe x -> x.max
| Talentstilsonderfertigkeit x -> x.max
| ErweiterteTalentsonderfertigkeit x -> x.max
| Kugelzauber x -> x.max
| Kesselzauber x -> x.max
| Kappenzauber x -> x.max
| Spielzeugzauber x -> x.max
| Schalenzauber x -> x.max
| SexSchicksalspunkteSonderfertigkeit x -> x.max
| SexSonderfertigkeit x -> x.max
| Waffenzauber x -> x.max
| Sichelritual x -> x.max
| Ringzauber x -> x.max
| Chronikzauber x -> x.max
+37
View File
@@ -0,0 +1,37 @@
(**
* Convenient accessor functions for static Activatable entries.
*
* This module provides some accessor functions for accessing properties of a
* static Activatable entry. The functions are named so that the name equals the
* property name.
*
* It also provides a few functions for simple derived values.
*)
val isActive : 'a Activatable_Dynamic.t -> bool
(**
* Is an Activatable entry active?
*)
val isActiveM : 'a Activatable_Dynamic.t option -> bool
(**
* Is an Activatable, where the entry may not have been created, active?
*)
val id : Activatable.t -> Id.Activatable.t
val id' : Activatable.t -> int
val idDeepVariant : Activatable.t -> Id.Activatable.Nested.t
val name : Activatable.t -> string
val selectOptions : Activatable.t -> SelectOption.map
val input : Activatable.t -> string option
val apValue : Activatable.t -> Activatable_Shared.ApValue.t option
val apValue' : Activatable.t -> int OneOrMany.t option
val max : Activatable.t -> int option
+60
View File
@@ -0,0 +1,60 @@
type t = {
naming: Activatable_Active_Name.combinedName,
active: Activatable_Convert.singleWithId,
apValue: int,
isAutomatic: bool,
validation: Activatable_Active_Validation.t,
staticEntry: Activatable.t,
heroEntry: Activatable_Dynamic.t,
};
let getActive =
(
~isEntryToAdd,
~addLevelToName,
cache,
staticData,
hero,
staticEntry,
heroEntry,
singleEntry,
) => {
let naming =
Activatable_Active_Name.getName(
~addLevelToName,
staticData,
staticEntry,
singleEntry,
);
let validation =
Activatable_Active_Validation.isRemovalOrModificationValid(
cache,
staticData,
hero,
staticEntry,
heroEntry,
singleEntry,
);
let {Activatable_Active_AdventurePointValue.apValue, isAutomatic} =
Activatable_Active_AdventurePointValue.getApValueDifferenceOnChange(
~isEntryToAdd,
~automaticAdvantages=cache.automaticAdvantages,
staticData,
hero,
staticEntry,
heroEntry,
singleEntry,
);
{
naming,
active: singleEntry,
apValue,
isAutomatic,
validation,
staticEntry,
heroEntry,
};
};
+27
View File
@@ -0,0 +1,27 @@
type t = {
naming: Activatable_Active_Name.combinedName,
active: Activatable_Convert.singleWithId,
apValue: int,
isAutomatic: bool,
validation: Activatable_Active_Validation.t,
staticEntry: Activatable.t,
heroEntry: Activatable_Dynamic.t,
};
/**
* `getActive cache staticData hero staticEntry maybeHeroEntry` adds display
* options to an active entry, such as calculated AP, combined/generated name or
* if it's valid to remove it.
*/
let getActive:
(
~isEntryToAdd: bool,
~addLevelToName: bool,
Activatable_Cache.t,
Static.t,
Hero.t,
Activatable.t,
Activatable_Dynamic.t,
Activatable_Convert.singleWithId
) =>
t;
@@ -0,0 +1,590 @@
module L = Ley_List;
open Ley_Option.Infix;
open Ley_Function;
open Activatable_Convert;
open Activatable_SelectOptions;
type combinedApValue = {
apValue: int,
isAutomatic: bool,
};
let ensureFlat =
fun
| Advantage.Static.Flat(x) => Some(x)
| PerLevel(_) => None;
let ensurePerLevel =
fun
| Advantage.Static.Flat(_) => None
| PerLevel(x) => Some(x);
let getDefaultApValue = (staticEntry, singleHeroEntry) => {
open Ley_List;
let sid1 = singleHeroEntry |> getOption1;
let level = Ley_Option.fromOption(1, singleHeroEntry.level);
let apValue =
staticEntry
|> Activatable_Accessors.apValue
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
let optionApValue =
sid1
>>= Activatable_Convert.activatableOptionToSelectOptionId
>>= getSelectOptionCost(staticEntry);
switch (optionApValue) {
| Some(x) => Some(x)
| None =>
switch (apValue) {
| Flat(x) => Some(x * level)
| PerLevel(xs) =>
switch (staticEntry) {
| Advantage(_)
| Disadvantage(_) => Ley_List.Safe.atMay(xs, level - 1)
| SpecialAbility(_) =>
xs |> take(Ley_Int.max(1, level)) |> Ley_List.sum |> (x => Some(x))
}
}
};
};
let getPrinciplesObligationsMaxLevels = ({active, _}: Activatable_Dynamic.t) =>
active
|> Ley_List.foldr(
(active: Activatable_Dynamic.single, (prevMax, prevSndMax)) =>
switch (active.level, active.customCost) {
// Only get the maximum from the current and the previous level, if
// the current has no custom cost
| (Some(activeLevel), None) =>
if (activeLevel > prevMax) {
(activeLevel, prevMax);
} else {
(prevMax, prevSndMax);
}
// Otherwise always return the previous max
| _ => (prevMax, prevSndMax)
},
(0, 0),
);
module IcAsIndex = {
// Helper function to lookup entry, it's IC, converting it to an index and get
// the correct value from the list of AP values
let%private getApValueByIcAsIndexAux = (mp, getIc, id, apValues) =>
Ley_IntMap.lookup(id, mp)
>>= (
static =>
static |> getIc |> IC.icToIndex |> Ley_List.Safe.atMay(apValues)
);
// Shortcut function for skills
let%private getApValueFromSkillsMapByIc = (staticData: Static.t) =>
getApValueByIcAsIndexAux(staticData.skills, skill => skill.ic);
// Shortcut function for combat techniques
let%private getApValueFromCombatTechniquesMapByIc = (staticData: Static.t) =>
getApValueByIcAsIndexAux(staticData.combatTechniques, skill => skill.ic);
// Shortcut function for spells
let%private getApValueFromSpellsMapByIc = (staticData: Static.t) =>
getApValueByIcAsIndexAux(staticData.spells, skill => skill.ic);
// Shortcut function for liturgical chants
let%private getApValueFromLiturgicalChantsMapByIc = (staticData: Static.t) =>
getApValueByIcAsIndexAux(staticData.liturgicalChants, skill => skill.ic);
/**
* `getApValueByIcAsIndex staticData apValue sid` takes the static data, the
* AP value of the current entry and it's first option. It uses the IC of the
* entry defined by the option as an index to retrieve the AP value from the
* list of AP values defined for the entry. If the selected entry is not
* present or if the entry does not define a list of AP values, `None` is
* returned.
*/
let getApValueByIcAsIndex =
(
staticData: Static.t,
apValue: Advantage.Static.apValue,
sid: Id.Activatable.Option.t,
) =>
[@warning "-4"]
(
switch (sid, apValue) {
| (Preset(Skill(id)), PerLevel(apValues)) =>
getApValueFromSkillsMapByIc(staticData, id, apValues)
| (Preset(CombatTechnique(id)), PerLevel(apValues)) =>
getApValueFromCombatTechniquesMapByIc(staticData, id, apValues)
| (Preset(Spell(id)), PerLevel(apValues)) =>
getApValueFromSpellsMapByIc(staticData, id, apValues)
| (Preset(LiturgicalChant(id)), PerLevel(apValues)) =>
getApValueFromLiturgicalChantsMapByIc(staticData, id, apValues)
| _ => None
}
);
};
/**
* Returns the value(s) how the spent AP value would change after removing the
* respective entry.
*
* @param isEntryToAdd If `entry` has not been added to the list of active
* entries yet, this must be `true`, otherwise `false`.
*/
let getApValueDifferenceOnChangeByEntry =
(
~isEntryToAdd,
staticData: Static.t,
hero: Hero.t,
staticEntry,
heroEntry: Activatable_Dynamic.t,
singleHeroEntry,
) => {
open Ley_List;
let sid1 = singleHeroEntry |> getOption1;
let level = singleHeroEntry.level;
let apValue =
staticEntry
|> Activatable_Accessors.apValue
|> Ley_Option.fromOption(Advantage.Static.Flat(0));
[@warning "-4"]
(
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
| Advantage(Aptitude)
| Advantage(ExceptionalSkill)
| Advantage(ExceptionalCombatTechnique)
| Advantage(WeaponAptitude)
| Disadvantage(Incompetent)
| SpecialAbility(AdaptionZauber)
| SpecialAbility(FavoriteSpellwork)
| SpecialAbility(Lieblingsliturgie)
| SpecialAbility(Forschungsgebiet)
| SpecialAbility(Expertenwissen)
| SpecialAbility(Wissensdurst)
| SpecialAbility(WegDerGelehrten)
| SpecialAbility(WegDerKuenstlerin)
| SpecialAbility(Fachwissen) =>
singleHeroEntry
|> getOption1
>>= IcAsIndex.getApValueByIcAsIndex(staticData, apValue)
| Disadvantage(PersonalityFlaw) =>
switch (sid1) {
| Some(Preset(Generic(selected_option))) =>
let matchOption = (target_option, current) =>
switch ((current: option(Id.Activatable.Option.t))) {
| Some(Preset(Generic(x))) => x === target_option
| _ => false
};
let isPersonalityFlawNotPaid = (target_option, paid_entries_max) =>
target_option === selected_option
&& Ley_List.countBy(
(e: Activatable_Dynamic.single) =>
e.options
|> Ley_Option.listToOption
|> matchOption(target_option)
// Entries with custom cost are ignored for the rule
&& Ley_Option.isNone(e.customCost),
heroEntry.active,
)
> (isEntryToAdd ? paid_entries_max - 1 : paid_entries_max);
// 7 = "Prejudice" => more than one entry possible
// more than one entry of Prejudice does not contribute to AP spent
//
// 8 = "Unworldly" => more than one entry possible
// more than two entries of Unworldly do not contribute to AP spent
//
// In both cases, removing the entry would not change AP so it has to
// return 0.
if (isPersonalityFlawNotPaid(7, 1) || isPersonalityFlawNotPaid(8, 2)) {
Some(0);
} else {
getSelectOptionCost(staticEntry, Generic(selected_option));
};
| _ => None
}
| Disadvantage(Principles)
| Disadvantage(Obligations) =>
level
>>= (
level => {
// This is the highest and the second-highest level of this entry at the
// moment.
let (maxLevel, sndMaxLevel) =
getPrinciplesObligationsMaxLevels(heroEntry);
// If there is more than one entry on the same level if this entry is
// active, it won't affect AP spent at all. Thus, if the entry is to
// be added, there must be at least one (> 0) entry for this rule to
// take effect.
//
// If the entry is not the one with the highest level, adding or
// removing it won't affect AP spent at all
if (maxLevel > level
|| countBy(
(e: Activatable_Dynamic.single) =>
Ley_Option.elem(level, e.level),
heroEntry.active,
)
> (isEntryToAdd ? 0 : 1)) {
None;
} else {
// Otherwise, the level difference results in the cost.
apValue |> ensureFlat <&> ( * )(level - sndMaxLevel);
};
}
)
| Disadvantage(BadHabit) =>
apValue
|> ensureFlat
|> Ley_Option.find(_ =>
countBy(
(e: Activatable_Dynamic.single) =>
Ley_Option.isNone(e.customCost),
heroEntry.active,
)
> (isEntryToAdd ? 2 : 3)
)
| SpecialAbility(SkillSpecialization) =>
sid1
>>= getSkillFromOption(staticData)
<&> (
skill =>
// Multiply number of final occurences of the
// same skill...
(
countBy(
(e: Activatable_Dynamic.single) =>
e.options
|> Ley_Option.listToOption
|> Ley_Option.elem(
Id.Activatable.Option.Preset(Skill(skill.id)),
)
// Entries with custom cost are ignored for the rule
&& Ley_Option.isNone(e.customCost),
heroEntry.active,
)
+ (isEntryToAdd ? 1 : 0)
)
// ...with the skill's IC
* IC.getApForActivatation(skill.ic)
)
| SpecialAbility(Language) =>
level
>>= (
fun
// Native Tongue (level 4) does not cost anything
| 4 => Some(0)
| level => apValue |> ensureFlat <&> ( * )(level)
)
| SpecialAbility(PropertyKnowledge)
| SpecialAbility(AspectKnowledge) =>
apValue
|> ensurePerLevel
>>= (
apPerLevel => {
// Ignore custom cost activations in terms of calculated cost
let amountActive =
countBy(
(e: Activatable_Dynamic.single) =>
Ley_Option.isNone(e.customCost),
heroEntry.active,
);
let index = amountActive + (isEntryToAdd ? 0 : (-1));
Ley_List.Safe.atMay(apPerLevel, index);
}
)
| SpecialAbility(TraditionWitches) =>
// There are two disadvantages that, when active, decrease the cost of
// this tradition by 10 AP each
let decreaseCost = (id, cost) =>
hero.disadvantages
|> Ley_IntMap.lookup(id)
|> Activatable_Accessors.isActiveM
? cost - 10 : cost;
apValue
|> ensureFlat
<&> (
flatAp =>
flatAp
|> decreaseCost(Id.Disadvantage.toInt(NoFlyingBalm))
|> decreaseCost(Id.Disadvantage.toInt(NoFamiliar))
);
| SpecialAbility(Recherchegespuer) =>
// The AP cost for this SA consist of two parts: AP based on the IC of
// the main subject (from "SA_531"/Wissensdurst) in addition to AP based
// on the IC of the side subject selected in this SA.
hero.specialAbilities
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Wissensdurst))
>>= (
wissensdurst =>
apValue
|> ensurePerLevel
>>= (
apPerLevel => {
let getCostFromHeroEntry = entry =>
entry
|> getOption1
>>= IcAsIndex.getApValueByIcAsIndex(
staticData,
PerLevel(apPerLevel),
);
Ley_Option.liftM2(
(+),
// Cost for side subject
getCostFromHeroEntry(singleHeroEntry),
// Cost for main subject from Wissensdurst
wissensdurst.active
|> Ley_Option.listToOption
>>= (
fst =>
fst
|> singleToSingleWithId(heroEntry, 0)
|> getCostFromHeroEntry
),
);
}
)
)
| SpecialAbility(LanguageSpecializations) =>
apValue
|> ensureFlat
>>= (
flatAp =>
sid1
>>= getGenericId
>>= (
languageId =>
hero.specialAbilities
|> Ley_IntMap.lookup(Id.SpecialAbility.toInt(Language))
>>= (
language =>
language.active
|> Ley_List.find((e: Activatable_Dynamic.single) =>
e.options
|> Ley_Option.listToOption
>>= getGenericId
|> Ley_Option.elem(languageId)
)
>>= (
selectedLanguage =>
selectedLanguage.level
<&> (
fun
| 4 => 0
| _ => flatAp
)
)
)
)
)
| SpecialAbility(Handwerkskunst)
| SpecialAbility(KindDerNatur)
| SpecialAbility(KoerperlichesGeschick)
| SpecialAbility(SozialeKompetenz)
| SpecialAbility(Universalgenie) =>
singleHeroEntry.options
|> Ley_List.take(3)
|> Ley_Option.mapOption(
IcAsIndex.getApValueByIcAsIndex(staticData, apValue),
)
|> Ley_List.sum
|> Ley_Option.ensure((<)(0))
| _ => getDefaultApValue(staticEntry, singleHeroEntry)
}
);
};
/**
* Returns the AP you get when addiing or removing the entry.
*
* @param isEntryToAdd If `entry` has not been added to the list of active
* entries yet, this must be `true`, otherwise `false`.
*/
let getApValueDifferenceOnChange =
(
~isEntryToAdd,
~automaticAdvantages,
staticData,
hero: Hero.t,
staticEntry,
heroEntry: Activatable_Dynamic.t,
singleHeroEntry,
) => {
let isAutomatic = Ley_List.elem(singleHeroEntry.id, automaticAdvantages);
let modifyAbs =
switch (staticEntry) {
| Activatable.Disadvantage(_) => Ley_Int.negate
| Advantage(_)
| SpecialAbility(_) => id
};
switch (singleHeroEntry.customCost) {
| Some(customCost) => {apValue: modifyAbs(customCost), isAutomatic}
| None =>
getApValueDifferenceOnChangeByEntry(
~isEntryToAdd,
staticData,
hero,
staticEntry,
heroEntry,
singleHeroEntry,
)
|> Ley_Option.fromOption(0)
|> modifyAbs
|> (apValue => {apValue, isAutomatic})
};
};
/**
* Returns the difference to the value from `getApValueDifferenceOnChange` to be
* able to calculate the correct AP spent.
*
* A positive return value means that the entry uses *more* AP than what
* `getApValueDifferenceOnChange` returned for all activations, so that both
* return values can be added to form the final AP spent.
*
* Note, that disadvantages still return positive values if they actually cost
* AP.
*/
let getApSpentDifference = (staticEntry, heroEntry: Activatable_Dynamic.t) => {
let apValue = staticEntry |> Activatable_Accessors.apValue;
[@warning "-4"]
(
switch (Activatable_Accessors.idDeepVariant(staticEntry)) {
| Disadvantage(Principles)
| Disadvantage(Obligations) =>
let (maxLevel, sndMaxLevel) =
getPrinciplesObligationsMaxLevels(heroEntry);
let singlesAtMaxLevel =
L.countBy(
fun
| ({level: Some(level), _}: Activatable_Dynamic.single) =>
level === maxLevel
| _ => false,
heroEntry.active,
);
let baseApValue =
switch (apValue) {
| Some(Flat(value)) => value
| Some(PerLevel(_))
| None => 0
};
singlesAtMaxLevel > 1
// In this case, all max level entries would be 0, so this needs to be
// the full value for the max level
? baseApValue * maxLevel
// otherwise, the max level entry costs the difference between max and
// second max, so we need to fill to actual value so that
// currentDifference + baseApValue * sndMaxLevel = total AP value
: baseApValue * sndMaxLevel;
| Disadvantage(PersonalityFlaw) =>
let counterBySelectOption =
heroEntry.active
|> SelectOption.Map.countByM(
fun
| (
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
) =>
Some(id)
| _ => None,
);
let getDiffBySelectOption = (selectOptionId, paidEntries, counter) => {
counter
|> SelectOption.Map.lookup(Generic(selectOptionId))
|> Ley_Option.fromOption(0) > paidEntries
? Activatable_SelectOptions.getSelectOptionCost(
staticEntry,
Generic(selectOptionId),
)
|> Ley_Option.option(0, apValue =>
apValue * paidEntries |> Ley_Int.negate
)
: 0;
};
getDiffBySelectOption(7, 1, counterBySelectOption)
+ getDiffBySelectOption(8, 2, counterBySelectOption);
| Disadvantage(BadHabit) =>
heroEntry.active
// Ignore entries with custom cost
|> Ley_List.countBy(
fun
| ({customCost: None, _}: Activatable_Dynamic.single) => true
| _ => false,
)
> 3
// if more than three entries are active, a 0 is displayed, so we need to
// keep the AP value for three entries.
? switch (apValue) {
| Some(Flat(flatApValue)) => flatApValue * (-3)
| _ => 0
}
: 0
| SpecialAbility(SkillSpecialization) =>
let counterBySelectOption =
heroEntry.active
|> SelectOption.Map.countByM(
fun
| (
{options: [Preset(id)], customCost: None, _}: Activatable_Dynamic.single
) =>
Some(id)
| _ => None,
);
counterBySelectOption
|> SelectOption.Map.foldrWithKey(
(id, count, accDiff) => {
switch (
Activatable_SelectOptions.getSelectOption(staticEntry, id)
) {
| Some({staticEntry: Some(Skill(skill)), _}) =>
// The sum of displayed AP values without taking IC into account.
let currentIcFactorForSkill = count * count;
// The sum of actual AP values without taking IC into account.
let actualIcFactorForSkill = Math.gsum(1, count);
// Apply IC multiplier to get final AP value
IC.getApForActivatation(skill.ic)
* (actualIcFactorForSkill - currentIcFactorForSkill)
+ accDiff;
| _ => accDiff
}
},
0,
);
| SpecialAbility(PropertyKnowledge)
| SpecialAbility(AspectKnowledge) =>
let entryCount = heroEntry.active |> Ley_List.length;
switch (apValue) {
| Some(PerLevel(apValues)) =>
let currentSum =
Ley_List.Safe.atMay(apValues, entryCount)
|> Ley_Option.option(0, ( * )(entryCount));
let actualSum = apValues |> Ley_List.take(entryCount) |> Ley_List.sum;
actualSum - currentSum;
| _ => 0
};
| _ => 0
}
);
};
@@ -0,0 +1,41 @@
/**
* Functions for working on adventure point values for Activatable entries.
*
* This module provides types and functions for calculating and keeping
* adventure point value calculations in genereal and specific to certain
* entries.
*/
open Activatable_Convert;
/**
* The generated AP value for an Activatable. Contains the generated AP value
* as well as if the entry is automatically added by the race, in which case
* no additional AP from the hero are needed.
*/
type combinedApValue = {
apValue: int,
isAutomatic: bool,
};
/**
* `getApValue isEntryToAdd automaticAdvantages staticData hero
* staticActivatable heroActivatable singleEntry` returns the AP you get when
* removing the passed `singleEntry`. It also returns if the entry has been
* automatically granted by the race.
*
* `isEntryToAdd` has to be `true` if `singleEntry` has not been added
* to the list of active entries yet, otherwise `false`. `automaticAdvantages`
* is the list of automatic advantage IDs.
*/
let getApValueDifferenceOnChange:
(
~isEntryToAdd: bool,
~automaticAdvantages: list(int),
Static.t,
Hero.t,
Activatable.t,
Activatable_Dynamic.t,
singleWithId
) =>
combinedApValue;
+507
View File
@@ -0,0 +1,507 @@
open Ley_Option;
open Ley_Option.Infix;
open Ley_Function;
open Activatable_Convert;
open Activatable_SelectOptions;
let getDefaultNameAddition = (staticEntry, heroEntry) => {
let input = Activatable_Accessors.input(staticEntry);
let selectOptions = Activatable_Accessors.selectOptions(staticEntry);
let sid = heroEntry |> getOption1;
let sid2 = heroEntry |> getOption2;
[@warning "-4"]
(
switch (input, sid, sid2) {
// Text input
| (Some(_), Some(CustomInput(str)), None) => Some(str)
// Select option and text input
| (Some(_), Some(Preset(id)), Some(CustomInput(str)))
when SelectOption.Map.size(selectOptions) > 0 =>
Some(
(id |> getSelectOptionName(staticEntry) |> fromOption(""))
++ ": "
++ str,
)
// Plain select option
| (None, Some(Preset(id)), None) => getSelectOptionName(staticEntry, id)
| _ => None
}
);
};
/**
* A lot of entries have customization options: Text input, select option or
* both. This function creates a string that can be appended to the `name`
* property of the respective record to create the full active name.
*/
let getEntrySpecificNameAddition =
(staticData: Static.t, staticEntry, heroEntry) =>
switch (staticEntry) {
| Activatable.Advantage(entry) =>
[@warning "-4"]
(
switch (Id.Advantage.fromInt(entry.id)) {
| Aptitude
| ExceptionalSkill =>
heroEntry
|> getOption1
>>= (
sid =>
switch (sid) {
| Preset(Skill(id)) =>
lookupMap(id, staticData.skills, x => x.name)
| Preset(Spell(id)) =>
lookupMap(id, staticData.spells, x => x.name)
| Preset(LiturgicalChant(id)) =>
lookupMap(id, staticData.liturgicalChants, x => x.name)
| Preset(_)
| CustomInput(_) => None
}
)
| ExceptionalCombatTechnique
| WeaponAptitude =>
heroEntry
|> getOption1
>>= (
sid =>
switch (sid) {
| Preset(CombatTechnique(id)) =>
lookupMap(id, staticData.combatTechniques, x => x.name)
| Preset(_)
| CustomInput(_) => None
}
)
| HatredFor =>
heroEntry
|> getOption1
>>= Activatable_Convert.activatableOptionToSelectOptionId
>>= getSelectOption(staticEntry)
|> liftM2(
(type_, frequency: SelectOption.t) =>
type_ ++ " (" ++ frequency.name ++ ")",
getOption2(heroEntry) >>= getCustomInput,
)
| _ => getDefaultNameAddition(staticEntry, heroEntry)
}
)
| Disadvantage(entry) =>
[@warning "-4"]
(
switch (Id.Disadvantage.fromInt(entry.id)) {
| Incompetent =>
heroEntry
|> getOption1
>>= getSkillFromOption(staticData)
<&> (x => x.name)
| PersonalityFlaw =>
heroEntry
|> getOption1
>>= Activatable_Convert.activatableOptionToSelectOptionId
>>= getSelectOption(staticEntry)
<&> (
option1 =>
(
switch (option1.id) {
// Get the input if Prejudice or Unworldly is selected
| Generic(7 | 8) => heroEntry |> getOption2 >>= getCustomInput
// Otherwise ignore any additional options
| _ => None
}
)
|> option(option1.name, specialInput =>
option1.name ++ ": " ++ specialInput
)
)
| _ => getDefaultNameAddition(staticEntry, heroEntry)
}
)
| SpecialAbility(entry) =>
[@warning "-4"]
(
switch (Id.SpecialAbility.fromInt(entry.id)) {
| AdaptionZauber
| FavoriteSpellwork =>
heroEntry
|> getOption1
>>= (
sid =>
switch (sid) {
| Preset(Spell(id)) =>
lookupMap(id, staticData.spells, x => x.name)
| Preset(_)
| CustomInput(_) => None
}
)
| TraditionSavant
| Forschungsgebiet
| Expertenwissen
| Wissensdurst
| Recherchegespuer =>
heroEntry
|> getOption1
>>= getSkillFromOption(staticData)
<&> (x => x.name)
| Lieblingsliturgie =>
heroEntry
|> getOption1
>>= (
sid =>
switch (sid) {
| Preset(LiturgicalChant(id)) =>
lookupMap(id, staticData.liturgicalChants, x => x.name)
| Preset(_)
| CustomInput(_) => None
}
)
| SkillSpecialization =>
heroEntry
|> getOption1
>>= getSkillFromOption(staticData)
>>= (
skill =>
heroEntry
|> getOption2
>>= (
option2 =>
(
switch (option2) {
// If input string use input
| CustomInput(x) => Some(x)
// Otherwise lookup application name
| Preset(Generic(id)) =>
skill.applications
|> Ley_IntMap.find((a: Skill.Static.Application.t) =>
a.id === id
)
<&> (a => a.name)
| Preset(_) => None
}
)
// Merge skill name and application name
<&> (appName => skill.name ++ ": " ++ appName)
)
)
| Exorzist =>
switch (heroEntry.level) {
| Some(1) =>
heroEntry
|> getOption1
>>= Activatable_Convert.activatableOptionToSelectOptionId
>>= getSelectOptionName(staticEntry)
| _ => None
}
| SpellEnhancement as entryId
| ChantEnhancement as entryId =>
heroEntry
|> getOption1
>>= Activatable_Convert.activatableOptionToSelectOptionId
>>= getSelectOption(staticEntry)
>>= (
enhancement =>
enhancement.enhancementTarget
>>= (
id =>
(
switch (entryId) {
| SpellEnhancement =>
Ley_IntMap.lookup(id, staticData.spells) <&> (x => x.name)
| _ =>
Ley_IntMap.lookup(id, staticData.liturgicalChants)
<&> (x => x.name)
}
)
<&> (targetName => targetName ++ ": " ++ enhancement.name)
)
)
| TraditionArcaneBard =>
heroEntry
|> getOption1
>>= getGenericId
>>= flip(Ley_IntMap.lookup, staticData.arcaneBardTraditions)
<&> (trad => trad.name)
| TraditionArcaneDancer =>
heroEntry
|> getOption1
>>= getGenericId
>>= flip(Ley_IntMap.lookup, staticData.arcaneDancerTraditions)
<&> (trad => trad.name)
| LanguageSpecializations =>
liftM2(
getSelectOption,
Ley_IntMap.lookup(
Id.SpecialAbility.toInt(Language),
staticData.specialAbilities,
)
<&> (specialAbility => Activatable.SpecialAbility(specialAbility)),
getOption1(heroEntry)
>>= Activatable_Convert.activatableOptionToSelectOptionId,
)
|> join
>>= (
language =>
heroEntry
|> getOption2
>>= (
option2 =>
(
switch (option2) {
| CustomInput(str) => Some(str)
| Preset(Generic(specializationId)) =>
language.specializations
>>= (
specializations =>
Ley_List.Safe.atMay(
specializations,
specializationId - 1,
)
)
| Preset(_) => None
}
)
<&> (specialization => language.name ++ ": " ++ specialization)
)
)
| Fachwissen =>
heroEntry
|> getOption1
>>= getSkillFromOption(staticData)
>>= (
skill => {
let applications =
skill.applications
|> Ley_IntMap.filter((app: Skill.Static.Application.t) =>
app.prerequisite |> isNone
);
[heroEntry |> getOption2, heroEntry |> getOption3]
|> mapOption(option =>
option
>>= getGenericId
>>= (
opt =>
applications
|> Ley_IntMap.find((app: Skill.Static.Application.t) =>
app.id === opt
)
<&> (app => app.name)
)
)
|> ensure(apps => apps |> Ley_List.length |> (===)(2))
<&> (
apps =>
apps
|> AdvancedFiltering.sortStrings(staticData)
|> Intl.ListFormat.format(Conjunction, staticData)
|> (appsStr => skill.name ++ ": " ++ appsStr)
);
}
)
| _ => getDefaultNameAddition(staticEntry, heroEntry)
}
)
};
let getDisAdvLevelStr = RomanInt.intToRoman;
let getSpecialAbilityLevelStr = level =>
(level > 1 ? "I" ++ Chars.nobr ++ "" ++ Chars.nobr : "")
++ getDisAdvLevelStr(level);
/**
* Gets the level string that hast to be appended (with a non-breaking space!)
* to the name. This string is aware of differences between dis/advantages and
* special abilties as well as it handles the Native Tongue level for
* languages.
*/
let getLevelName = (staticData: Static.t, staticEntry, singleHeroEntry) =>
switch (staticEntry, singleHeroEntry.level) {
| (Activatable.Advantage(_), Some(level))
| (Disadvantage(_), Some(level)) => Some(getDisAdvLevelStr(level))
| (SpecialAbility(staticEntry), Some(level)) =>
[@warning "-4"]
(
switch (Id.SpecialAbility.fromInt(staticEntry.id)) {
// Language level 4 = Native Tongue and thus needs a special name
| Language when level === 4 =>
Some(staticData.messages.specialabilities_nativetonguelevel)
| _ => Some(getSpecialAbilityLevelStr(level))
}
)
| (Advantage(_) | Disadvantage(_) | SpecialAbility(_), None) => None
};
/**
* Some entries cannot use the default `name` property from wiki entries. The
* value returned by may not use the default `name` property. For all entries
* that do not need to handle a specific display format, the default `name`
* property is used.
*/
let getEntrySpecificNameReplacements =
(~addLevelToName, staticEntry, nameAddition, levelName) => {
let name = Activatable_Accessors.name(staticEntry);
let flatLevelName =
addLevelToName ? option("", (++)(Chars.nbsp), levelName) : "";
let mapNameAddition = f => option(name, f, nameAddition);
let mapDefaultWithParens = () =>
mapNameAddition(add => name ++ " (" ++ add ++ ")");
let mapDefaultWithoutParens = () =>
mapNameAddition(add => name ++ " " ++ add);
let addSndInParens = snd =>
Ley_List.Extra.replaceStr(")", ": " ++ snd ++ ")");
switch (staticEntry) {
| Advantage(entry) =>
[@warning "-4"]
(
switch (Id.Advantage.fromInt(entry.id)) {
| ImmunityToPoison
| ImmunityToDisease
| HatredFor => mapDefaultWithoutParens()
| _ => mapDefaultWithParens() ++ flatLevelName
}
)
| Disadvantage(entry) =>
[@warning "-4"]
(
switch (Id.Disadvantage.fromInt(entry.id)) {
| AfraidOf => mapDefaultWithoutParens() ++ flatLevelName
| Principles
| Obligations =>
option(
name ++ flatLevelName,
nameAddition => name ++ flatLevelName ++ " (" ++ nameAddition ++ ")",
nameAddition,
)
| _ => mapDefaultWithParens() ++ flatLevelName
}
)
| SpecialAbility(entry) =>
[@warning "-4"]
(
switch (Id.SpecialAbility.fromInt(entry.id)) {
| GebieterDesAspekts => mapDefaultWithoutParens()
| TraditionArcaneBard
| TraditionArcaneDancer
| TraditionSavant => mapNameAddition(flip(addSndInParens, name))
| _ => mapDefaultWithParens() ++ flatLevelName
}
)
};
};
type combinedName = {
name: string,
baseName: string,
addName: option(string),
levelName: option(string),
};
/**
* Returns name, splitted and combined, of advantage/disadvantage/special
* ability as a Option (in case the wiki entry does not exist).
*/
let getName = (~addLevelToName, staticData, staticEntry, heroEntry) => {
let addName =
getEntrySpecificNameAddition(staticData, staticEntry, heroEntry);
let levelName = getLevelName(staticData, staticEntry, heroEntry);
let fullName =
getEntrySpecificNameReplacements(
~addLevelToName,
staticEntry,
addName,
levelName,
);
{
name: fullName,
baseName: Activatable_Accessors.name(staticEntry),
addName,
levelName,
};
};
/*
/**
* Returns the name of the given object. If the object is a string, it returns
* the string.
*/
export const getFullName =
(obj: string | ActiveActivatable): string => {
if (typeof obj === "string") {
return obj
}
return obj.nameAndCost.naming.name
}
/**
* Accepts the full special ability name and returns only the text between
* parentheses. If no parentheses were found, returns an empty string.
*/
export const getBracketedNameFromFullName =
(full_name: string): string => {
const result = /\((?<subname>.+)\)/u .exec (full_name)
if (result === null || result .groups === undefined) {
return ""
}
return result .groups .subname
}
/**
* `compressList :: L10n -> [ActiveActivatable] -> String`
*
* Takes a list of active Activatables and merges them together. Used to display
* lists of Activatables on character sheet.
*/
export const compressList =
(staticData: StaticDataRecord) =>
(xs: List<Record<ActiveActivatable>>): string => {
const grouped_xs =
elems (groupByKey<Record<ActiveActivatable>, string> (AAA_.id) (xs))
return pipe (
map (
ifElse<List<Record<ActiveActivatable>>>
(xs_group => flength (xs_group) === 1)
(pipe (listToOption, Option ("") (AAA_.name)))
(xs_group => pipe (
map ((x: Record<ActiveActivatable>) => {
const levelPart =
pipe (
AAA_.level,
fmap (pipe (toRoman, appendStr (" "))),
fromOption ("")
)
(x)
const selectOptionPart =
fromOption ("") (AAA_.addName (x))
return selectOptionPart + levelPart
}),
sortStrings (staticData),
intercalate (", "),
x => ` (${x})`,
x => option ("")
((r: Record<ActiveActivatable>) =>
AAA_.baseName (r) + x)
(listToOption (xs_group))
)
(xs_group))
),
sortStrings (staticData),
intercalate (", ")
)
(grouped_xs)
}
*/
@@ -0,0 +1,28 @@
/**
* Generate full names for an active Activatanle entry.
*
* This module provides types and functions to generate a full name string for
* an active Activatable entry.
*/
open Activatable_Convert;
/**
* The generated name for an Activatable. Contains the generated name and it's
* parts: The base entry name, the generated addition and the converted level
* name.
*/
type combinedName = {
name: string,
baseName: string,
addName: option(string),
levelName: option(string),
};
/**
* `getName addLevelToName staticData staticActivatable singleEntry` returns
* the name, splitted and combined, of the passed `singleEntry`.
*/
let getName:
(~addLevelToName: bool, Static.t, Activatable.t, singleWithId) =>
combinedName;
@@ -0,0 +1,449 @@
module I = Ley_Int;
module IM = Ley_IntMap;
module IS = Ley_IntSet;
module L = Ley_List;
module O = Ley_Option;
open Activatable_Cache;
type t = {
minLevel: option(int),
maxLevel: option(int),
disabled: bool,
};
// const hasRequiredMinimumLevel =
// (min_level: Maybe<number>) => (max_level: Maybe<number>): boolean =>
// isJust (max_level) && isJust (min_level)
let getActivesById = (mp, id) =>
IM.lookup(id, mp)
|> O.option([], ({active, _}: Activatable_Dynamic.t) => active);
let isNotRequiredByDependencies =
(
hero: Hero.t,
staticEntry,
heroEntry: Activatable_Dynamic.t,
singleEntry: Activatable_Convert.singleWithId,
) =>
heroEntry.dependencies
// Filter out every dependency that can be matched by another entry
|> Dependencies.Flatten.flattenActivatableDependencies(
switch (staticEntry) {
| Activatable.Advantage(_) => getActivesById(hero.advantages)
| Disadvantage(_) => getActivesById(hero.disadvantages)
| SpecialAbility(_) => getActivesById(hero.specialAbilities)
},
singleEntry.id,
)
|> L.all((dep: Activatable_Dynamic.dependency) =>
!
Dependencies.Activatable.isDependencyMatched(
dep,
Activatable_Convert.singleWithIdToSingle(singleEntry),
)
// If the entry matched the dependency, try if other activations of the
// same entry may still satisfy the dependency, so that it can still be
// removed
|| L.Index.deleteAt(singleEntry.index, heroEntry.active)
|> L.any(Dependencies.Activatable.isDependencyMatched(dep))
);
let isEntrySpecificRemovalValid =
(
cache,
staticData,
hero,
staticEntry,
heroEntry: Activatable_Dynamic.t,
singleEntry: Activatable_Convert.singleWithId,
) =>
if (Tradition.Magical.isTraditionId(staticData, singleEntry.id)) {
let activeTraditions = cache.magicalTraditions;
let hasMultipleTraditions = L.length(activeTraditions) > 1;
hasMultipleTraditions
|| !ActivatableSkills.hasActiveSkillEntries(Spells, hero)
&& IS.null(hero.cantrips);
} else if (Tradition.Blessed.isTraditionId(staticData, singleEntry.id)) {
!ActivatableSkills.hasActiveSkillEntries(LiturgicalChants, hero)
&& IS.null(hero.blessings);
} else {
switch (staticEntry) {
| Activatable.Advantage(staticAdvantage) =>
[@warning "-4"]
Id.Advantage.(
switch (fromInt(staticAdvantage.id)) {
| ExceptionalSkill =>
switch (singleEntry.options) {
| [Preset(Skill(id)), ..._] =>
// value of target skill
let value =
IM.lookup(id, hero.skills) |> Skill.Dynamic.getValueDef;
// amount of active Exceptional Skill advantages for the same skill
let countSameSkill =
L.countBy(
(active: Activatable_Dynamic.single) =>
switch (active.options) {
| [Preset(Skill(otherId)), ..._] => otherId === id
| _ => false
},
heroEntry.active,
);
// if the maximum skill rating is reached removal needs to be
// disabled
value >= cache.startExperienceLevel.maxSkillRating + countSameSkill;
| _ => true
}
| ExceptionalCombatTechnique =>
switch (singleEntry.options) {
| [Preset(CombatTechnique(id)), ..._] =>
// value of target combat technique
let value =
IM.lookup(id, hero.combatTechniques)
|> CombatTechnique.Dynamic.getValueDef;
// if the maximum combat technique rating is reached removal needs
// to be disabled
value >= cache.startExperienceLevel.maxCombatTechniqueRating + 1;
| _ => true
}
| _ => true
}
)
| Disadvantage(_) => true
| SpecialAbility(staticSpecialAbility) =>
[@warning "-4"]
Id.SpecialAbility.(
switch (fromInt(staticSpecialAbility.id)) {
| Literacy =>
switch (
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
cache.matchingLanguagesScripts.scriptsWithMatchingLanguages,
singleEntry.options,
) {
// requiring entry must be active, the list of scripts must contain
// only one element and that element has to match the current script
// to prohibit removing the entry
| (
true,
[onlyScriptWithLanguage],
[Preset(Generic(scriptId)), ..._],
) =>
onlyScriptWithLanguage !== scriptId
| _ => true
}
| Language =>
switch (
cache.matchingLanguagesScripts.isEntryActiveRequiringMatch,
cache.matchingLanguagesScripts.languagesWithMatchingScripts,
singleEntry.options,
) {
// requiring entry must be active, the list of languages must contain
// only one element and that element has to match the current language
// to prohibit removing the entry
| (
true,
[onlyLanguageWithScript],
[Preset(Generic(languageId)), ..._],
) =>
onlyLanguageWithScript !== languageId
| _ => true
}
| PropertyKnowledge =>
switch (singleEntry.options) {
| [Preset(Generic(propertyId)), ..._] =>
hero.spells
// If there is any spell with matching property above SR 14...
|> IM.any((heroSpell: ActivatableSkill.Dynamic.t) =>
ActivatableSkill.Dynamic.valueToInt(heroSpell.value) > 14
&& IM.lookup(heroSpell.id, staticData.spells)
|> O.option(true, ({Spell.Static.property, _}) =>
property === propertyId
)
)
// ...prohibit removal
|> (!)
| _ => true
}
| AspectKnowledge =>
let activeAspects =
Activatable_SelectOptions.mapActiveOptions1(
fun
| Preset(Generic(aspectId)) => Some(aspectId)
| _ => None,
heroEntry,
)
|> IS.fromList;
switch (singleEntry.options) {
| [Preset(Generic(aspectId)), ..._] =>
let otherAspects = IS.delete(aspectId, activeAspects);
hero.liturgicalChants
// If there is any liturgical chant with matching aspect
// above SR 14 and no other aspect knowledges for that entry...
|> IM.any((heroLiturgicalChant: ActivatableSkill.Dynamic.t) =>
ActivatableSkill.Dynamic.valueToInt(
heroLiturgicalChant.value,
)
> 14
&& IM.lookup(
heroLiturgicalChant.id,
staticData.liturgicalChants,
)
|> O.option(true, ({LiturgicalChant.Static.aspects, _}) =>
IS.member(aspectId, aspects)
&& IS.disjoint(otherAspects, aspects)
)
)
// ...prohibit removal
|> (!);
| _ => true
};
| CombatStyleCombination =>
let activeArmedStyles = cache.armedCombatStylesCount;
let activeUnarmedStyles = cache.unarmedCombatStylesCount;
let totalActive = activeArmedStyles + activeUnarmedStyles;
// default is 1 per group (armed/unarmed), but with this SA 1 more in
// one group: maximum of 3, but max 2 per group. If max is reached,
// this SA cannot be removed
totalActive < 3 && (activeArmedStyles < 2 || activeUnarmedStyles < 2);
| MagicStyleCombination =>
// default is 1, but with this SA its 2. If it's 2 this SA is
// neccessary and cannot be removed
cache.magicalStylesCount < 2
| Zugvoegel
| JaegerinnenDerWeissenMaid
| AnhaengerDesGueldenen =>
switch (cache.blessedTradition) {
| Some((_, _, blessedTradition)) =>
hero.liturgicalChants
// Filter liturgical chants...
|> IM.mapMaybe(
fun
// ...so that only the active ones remain...
| {ActivatableSkill.Dynamic.id, value: Active(_), _} =>
// ...and replace with their static entry.
IM.lookup(id, staticData.liturgicalChants)
| _ => None,
)
// Check if there is any active entry that does not belong to the
// active tradition...
|> IM.any(({LiturgicalChant.Static.traditions, _}) =>
IS.notElem(blessedTradition.numId, traditions)
)
// ...which would prohibit removal
|> (!)
| None => true
}
| _ => true
}
)
};
};
let isStyleSpecialAbilityRemovalValid = (hero, staticEntry) =>
switch (staticEntry) {
| Activatable.Advantage(_)
| Disadvantage(_) => true
| SpecialAbility(specialAbility) =>
Activatable_ExtendedStyle.isStyleValidToRemove(hero, specialAbility)
};
/**
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
* level for an "increase entry", which is an entry that allows to get more
* specific values or entries than usually allowed. `currentAmount` is the
* current amount of specific values or entries used and `defaultAmount` the
* default amount allowed.
*/
let getMinLevelForIncreaseEntry = (defaultAmount, currentAmount) =>
currentAmount > defaultAmount ? Some(currentAmount - defaultAmount) : None;
/**
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
* level for a "decrease entry", which is an entry that disallows to get as many
* specific values or entries as usually allowed. `currentAmount` is the current
* amount of specific values or entries used and `defaultAmount` the default
* amount allowed.
*/
let getMaxLevelForDecreaseEntry = (maxDecrease, current) =>
I.max(0, maxDecrease - current);
let getEntrySpecificMinLevel =
(cache, staticData: Static.t, hero, staticEntry) =>
switch (staticEntry) {
| Activatable.Advantage(staticAdvantage) =>
[@warning "-4"]
Id.Advantage.(
switch (fromInt(staticAdvantage.id)) {
| LargeSpellSelection =>
hero
|> ActivatableSkills.countActiveSkillEntries(Spells)
|> getMinLevelForIncreaseEntry(3)
| ZahlreichePredigten =>
EntryGroups.SpecialAbility.countActiveFromGroup(
Predigten,
cache.specialAbilityPairs,
)
|> getMinLevelForIncreaseEntry(3)
| ZahlreicheVisionen =>
EntryGroups.SpecialAbility.countActiveFromGroup(
Visionen,
cache.specialAbilityPairs,
)
|> getMinLevelForIncreaseEntry(3)
| _ => None
}
)
| Disadvantage(_) => None
| SpecialAbility(staticSpecialAbility) =>
[@warning "-4"]
Id.SpecialAbility.(
switch (fromInt(staticSpecialAbility.id)) {
| Imitationszauberei =>
hero.spells
|> IM.countWith(({ActivatableSkill.Dynamic.id, _}) =>
IM.lookup(id, staticData.spells)
|> O.option(false, ({Spell.Static.gr, _}) =>
gr === Id.Spell.Group.toInt(Spells)
)
)
// Minimum level must be the number of active spells, if there are any
|> O.ensure(count => count > 0)
| _ => None
}
)
};
let getEntrySpecificMaxLevel = (cache, hero, staticEntry) =>
switch (staticEntry) {
| Activatable.Advantage(_) => None
| Disadvantage(staticDisadvantage) =>
[@warning "-4"]
Id.Disadvantage.(
switch (fromInt(staticDisadvantage.id)) {
| SmallSpellSelection =>
hero
|> ActivatableSkills.countActiveSkillEntries(Spells)
|> getMaxLevelForDecreaseEntry(3)
|> O.return
| WenigePredigten =>
EntryGroups.SpecialAbility.countActiveFromGroup(
Predigten,
cache.specialAbilityPairs,
)
|> getMaxLevelForDecreaseEntry(3)
|> O.return
| WenigeVisionen =>
EntryGroups.SpecialAbility.countActiveFromGroup(
Visionen,
cache.specialAbilityPairs,
)
|> getMaxLevelForDecreaseEntry(3)
|> O.return
| _ => None
}
)
| SpecialAbility(staticSpecialAbility) =>
[@warning "-4"]
Id.SpecialAbility.(
switch (fromInt(staticSpecialAbility.id)) {
| DunklesAbbildDerBuendnisgabe =>
O.Infix.(hero.pact <&> (pact => pact.level))
| _ => None
}
)
};
let adjustMinLevelByDependencies =
({Activatable_Dynamic.dependencies, _}, singleEntry, maybeMinLevel) =>
L.foldr(
(dependency: Activatable_Dynamic.dependency, maybeMinLevel) =>
switch (dependency.level) {
| Some(definedLevel) =>
// get the level dependency from the object and ensure it's
// greater than the current minimum level and that...
definedLevel > O.sum(maybeMinLevel)
// ...if the dependency defines options, too, the entry must match them
// as well. A dependency without options is valid for all entries (in
// case of calculating a minimum level)
&& Dependencies.Activatable.areOptionDependenciesMatched(
dependency,
Activatable_Convert.singleWithIdToSingle(singleEntry),
)
? Some(definedLevel) : maybeMinLevel
| None => maybeMinLevel
},
maybeMinLevel,
dependencies,
);
/**
* Get minimum valid level.
*/
let getMinLevel =
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) =>
getEntrySpecificMinLevel(cache, staticData, hero, staticEntry)
|> adjustMinLevelByDependencies(heroEntry, singleEntry);
let getMaxLevel = (cache, staticData, hero, staticEntry) => {
let entrySpecificMaxLevel =
getEntrySpecificMaxLevel(cache, hero, staticEntry);
let maxPossibleWithPrerequisites =
Prerequisites.Validation.getMaxLevel(
staticData,
hero,
staticEntry |> Activatable_Accessors.id |> Id.Activatable.toAll,
Prerequisites.Activatable.getLevelPrerequisites(staticEntry),
);
O.Infix.(
O.liftM2(I.min, entrySpecificMaxLevel, maxPossibleWithPrerequisites)
<|> entrySpecificMaxLevel
<|> maxPossibleWithPrerequisites
);
};
/**
* Checks if the given active entry can be removed or changed in level.
*/
let isRemovalOrModificationValid =
(cache, staticData, hero, staticEntry, heroEntry, singleEntry) => {
let minLevel =
getMinLevel(cache, staticData, hero, staticEntry, heroEntry, singleEntry);
let maxLevel = getMaxLevel(cache, staticData, hero, staticEntry);
{
disabled:
O.isSome(minLevel)
|| !
isNotRequiredByDependencies(
hero,
staticEntry,
heroEntry,
singleEntry,
)
|| !isStyleSpecialAbilityRemovalValid(hero, staticEntry)
|| !
isEntrySpecificRemovalValid(
cache,
staticData,
hero,
staticEntry,
heroEntry,
singleEntry,
),
maxLevel,
minLevel,
};
};
@@ -0,0 +1,39 @@
type t = {
minLevel: option(int),
maxLevel: option(int),
disabled: bool,
};
/**
* `getMinLevelForIncreaseEntry defaultAmount currentAmount` returns the minimum
* level for an "increase entry", which is an entry that allows to get more
* specific values or entries than usually allowed. `currentAmount` is the
* current amount of specific values or entries used and `defaultAmount` the
* default amount allowed.
*/
let getMinLevelForIncreaseEntry: (int, int) => option(int);
/**
* `getMaxLevelForDecreaseEntry defaultAmount currentAmount` returns the maximum
* level for a "decrease entry", which is an entry that disallows to get as many
* specific values or entries as usually allowed. `currentAmount` is the current
* amount of specific values or entries used and `defaultAmount` the default
* amount allowed.
*/
let getMaxLevelForDecreaseEntry: (int, int) => int;
/**
* `isRemovalOrModificationValid cache staticData hero staticEntry heroEntry
* singleEntry` checks if the given active entry can be removed or if it's level
* can be changed.
*/
let isRemovalOrModificationValid:
(
Activatable_Cache.t,
Static.t,
Hero.t,
Activatable.t,
Activatable_Dynamic.t,
Activatable_Convert.singleWithId
) =>
t;
+35
View File
@@ -0,0 +1,35 @@
/**
* A set of values to validate `Weg der Schreiberin`.
*/
type matchingLanguagesScripts = {
isEntryActiveRequiringMatch: bool,
languagesWithMatchingScripts: list(int),
scriptsWithMatchingLanguages: list(int),
};
/**
* The cache of computed values to optimize performance of activatable list
* generation.
*/
type t = {
startExperienceLevel: ExperienceLevel.t,
combatStyleCombination: option(Activatable_Dynamic.t),
armedCombatStylesCount: int,
unarmedCombatStylesCount: int,
magicalStylesCount: int,
isBlessedStyleActive: bool,
magicalStyleCombination: option(Activatable_Dynamic.t),
dunklesAbbild: option(Activatable_Dynamic.t),
activePactGiftsCount: int,
matchingLanguagesScripts,
validExtendedSpecialAbilities: list(int),
requiredApplyToMagicalActions: bool,
specialAbilityPairs:
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))),
combatTechniquePairs:
Ley_IntMap.t((CombatTechnique.Static.t, option(Skill.Dynamic.t))),
adventurePoints: AdventurePoints.categories,
magicalTraditions: list(Tradition.Magical.fullTradition),
blessedTradition: option(Tradition.Blessed.fullTradition),
automaticAdvantages: list(int),
};
+109
View File
@@ -0,0 +1,109 @@
type single_with_id = {
id : int;
index : int;
options : Id.Activatable.Option.t list;
level : int option;
customCost : int option;
}
(** A single active Activatable. *)
let single_to_single_with_id (x : 'a Activatable_Dynamic.t) index
(s : Activatable_Dynamic.single) =
{
id = x.id;
index;
options = s.options;
level = s.level;
customCost = s.customCost;
}
let hero_entry_to_singles (x : 'a Activatable_Dynamic.t) =
x.active |> Ley_List.Index.imap (single_to_single_with_id x)
let single_with_id_to_single s =
{
Activatable_Dynamic.options = s.options;
level = s.level;
customCost = s.customCost;
}
(* (** Converts the object generated by the list item to an object that can be
inserted into an array of ActiveObjects.
@param obj The entry for which you want to convert the object.
@param activate The object generated by the list item. *)
let convertUIStateToActiveObject =
(activate: Record<ActivatableActivationOptions>): Record<ActiveObject> => {
const id = AAOA.id (activate)
const selectOptionId1 = AAOA.selectOptionId1 (activate)
const selectOptionId2 = AAOA.selectOptionId2 (activate)
const selectOptionId3 = AAOA.selectOptionId3 (activate)
const input = AAOA.input (activate)
const level = AAOA.level (activate)
const customCost = AAOA.customCost (activate)
return id === AdvantageId.HatredOf
? ActiveObject ({
sid: selectOptionId1,
sid2: input,
cost: customCost,
})
: id === DisadvantageId.PersonalityFlaw
? ActiveObject ({
sid: selectOptionId1,
sid2: or (fmap (elemF (List<number | string> (7, 8)))
(selectOptionId1))
? input
: None,
cost: customCost,
})
: id === SpecialAbilityId.SkillSpecialization
? ActiveObject ({
sid: selectOptionId1,
sid2: alt<number | string> (input)
(selectOptionId2),
cost: customCost,
})
: isSome (input) && isSome (selectOptionId1)
? ActiveObject ({
sid: selectOptionId1,
sid2: input,
sid3: selectOptionId2,
tier: level,
cost: customCost,
})
: ActiveObject ({
sid: alt<number | string> (input)
(selectOptionId1),
sid2: then (guard (isSome (input) || isSome (selectOptionId1)))
(selectOptionId2),
sid3: selectOptionId3,
tier: level,
cost: customCost,
})
} *)
(* /**
* Generates a list of ActiveObjects based on the given instance.
*/
export const convertActivatableToArray =
(x: Record<ActivatableDependent>) =>
pipe_ (
x,
ADA.active,
imap (index => toActiveObjectWithId (index) (ADA.id (x)))
) *)
(* /**
* Get all active items in an array.
* @param state A state slice.
*/
export const getActiveFromState =
foldr (pipe (convertActivatableToArray, append)) (empty)
export interface ActiveObjectAny extends ActiveObject {
[key: string]: any
} *)
let activatable_option_to_select_option_id = function
| Id.Activatable.Option.Preset id -> Some id
| CustomInput _ -> None
+29
View File
@@ -0,0 +1,29 @@
(** Convert specific values to be able to easier work with them.
This function provides some utility functions to convert some types from the
state into smaller chuncks that are more usable for working with single
entries. *)
type single_with_id = {
id : int;
index : int;
options : Id.Activatable.Option.t list;
level : int option;
customCost : int option;
}
(** A single active Activatable. *)
val hero_entry_to_singles : 'a Activatable_Dynamic.t -> single_with_id list
(** Converts an Activatable hero entry into a "flattened" list of it's
activations. *)
val single_to_single_with_id :
'a Activatable_Dynamic.t ->
int ->
Activatable_Dynamic.single ->
single_with_id
val activatable_option_to_select_option_id :
Id.Activatable.Option.t -> Id.Activatable.SelectOption.t option
val single_with_id_to_single : single_with_id -> Activatable_Dynamic.single
+64
View File
@@ -0,0 +1,64 @@
type single = {
options : Id.Activatable.Option.t list;
level : int option;
customCost : int option;
}
type dependency = {
source : Id.Activatable.t;
target : int OneOrMany.t;
active : bool;
options : Id.Activatable.SelectOption.t OneOrMany.t list;
level : int option;
}
type 'static t = {
id : int;
active : single list;
dependencies : dependency list;
static : 'static option;
}
let empty static id = { id; active = []; dependencies = []; static }
let is_empty (x : 'a t) = Ley_List.null x.active && Ley_List.null x.dependencies
let is_active (x : 'a t) = Ley_List.Extra.notNull x.active
let is_active' x = Ley_Option.option false is_active x
module type S = sig
type static
type nonrec t = static t
(** The dynamic values in a character with a reference to the static values
from the database. *)
val empty : static option -> int -> t
(** [empty id] creates a new dynamic entry from an id. *)
val is_empty : t -> bool
(** [is_empty x] checks if the passed dynamic entry is empty. *)
val is_active : t -> bool
(** [is_active x] checks if the passed dynamic entry is active. *)
val is_active' : t option -> bool
(** [is_active' x] checks if the passed optional dynamic entry is active. *)
end
module Make (S : sig
type static
end) : S with type static = S.static = struct
type static = S.static
type nonrec t = static t
let empty = empty
let is_empty = is_empty
let is_active = is_active
let is_active' = is_active'
end
@@ -0,0 +1,398 @@
open Ley_Option;
/**
* Returns a getter and a setter for the appropiate dependency list for the
* passed style special ability or extended special ability.
*/
let getStyleDependenciesAcc = (style: SpecialAbility.Static.t) =>
[@warning "-4"]
(
switch (Id.SpecialAbility.Group.fromInt(style.gr)) {
| CombatStylesArmed
| CombatStylesUnarmed
| CombatExtended =>
Some((
((hero: Hero.t) => hero.combatStyleDependencies),
(
(hero: Hero.t, dependencies) => {
...hero,
combatStyleDependencies: dependencies,
}
),
))
| MagicalStyles
| MagicalExtended =>
Some((
((hero: Hero.t) => hero.magicalStyleDependencies),
(
(hero: Hero.t, dependencies) => {
...hero,
magicalStyleDependencies: dependencies,
}
),
))
| BlessedStyles
| KarmaExtended =>
Some((
((hero: Hero.t) => hero.blessedStyleDependencies),
(
(hero: Hero.t, dependencies) => {
...hero,
blessedStyleDependencies: dependencies,
}
),
))
| SkillStyles
| SkillExtended =>
Some((
((hero: Hero.t) => hero.skillStyleDependencies),
(
(hero: Hero.t, dependencies) => {
...hero,
skillStyleDependencies: dependencies,
}
),
))
| _ => None
}
);
/**
* If a style has multiple possible extended special abilities in one slot,
* it has more options to offer. If such a slot is used and a new style
* dependency with the used slot, but not multiple possibilities, is added,
* this function will shift the use to the style dependency with the fixed
* option instead, so there are more options left in the end.
*/
let moveActiveInListToNew = (newxs, x: Hero.styleDependency) =>
switch (x.id, x.active) {
// If the dependency has got a list of possible ids and is used, we check
// if the used id is included in the new dependencies as a dependency
// without a list of options to make more special abilities possible
| (Many(_), Some(active)) =>
// If a Some, it is the index in the list of new dependencies where we can
// put the active dependency instead
let index =
Ley_List.findIndex(
(newx: Hero.styleDependency) =>
switch (newx.id) {
| One(id) => id === active
| Many(_) => false
},
newxs,
);
switch (index) {
| Some(index) => (
Ley_List.Index.modifyAt(
index,
(newx: Hero.styleDependency) => {...newx, active: Some(active)},
newxs,
),
{...x, active: None},
)
| None => (newxs, x)
};
| (Many(_), None)
| (One(_), _) => (newxs, x)
};
/**
* `generateStyleDependencies heroSpecialAbilities styleSpecialAbility`
* returns generated style dependencies for a style special ability.
*/
let generateStyleDependencies =
(heroSpecialAbilities, styleSpecialAbility: SpecialAbility.Static.t) =>
[@warning "-4"]
(
switch (styleSpecialAbility.groupSpecific) {
| CombatStyle({extended: (ext1, ext2, ext3), _})
| GeneralStyle({extended: (ext1, ext2, ext3), _}) =>
Ley_Option.Infix.(
[ext1, ext2, ext3]
|> Ley_List.map(({SpecialAbility.Static.Extended.id, _}) =>
(
{id: One(id), active: None, origin: styleSpecialAbility.id}: Hero.styleDependency
)
)
|> (
xs =>
[@warning "-4"]
(
switch (Id.SpecialAbility.fromInt(styleSpecialAbility.id)) {
// For this style, the user must choose between two special
// abilities to be an extended special ability.
| ScholarDesMagierkollegsZuHoningen =>
Ley_IntMap.lookup(
styleSpecialAbility.id,
heroSpecialAbilities,
)
>>= (
(x: Activatable_Dynamic.t) =>
x.active |> Ley_Option.listToOption
)
>>= (x => Ley_List.Safe.atMay(x.options, 1))
|> (
fun
| Some(Preset(SpecialAbility(id))) => [
{
Hero.id: One(id),
active: None,
origin: styleSpecialAbility.id,
},
...xs,
]
| Some(_)
| None => xs
)
| _ => xs
}
)
)
|> Ley_Option.return
)
| _ => None
}
);
/**
* Adds extended special ability dependencies if the passed entry is a style
* special ability.
*/
let addStyleExtendedSpecialAbilityDependencies = (styleSpecialAbility, hero) =>
liftM2(
((get, set), newxs) =>
hero
|> get
|> Ley_List.mapAccumL(moveActiveInListToNew, newxs)
|> (((xs1, xs2)) => xs1 @ xs2)
|> set(hero),
getStyleDependenciesAcc(styleSpecialAbility),
generateStyleDependencies(hero.specialAbilities, styleSpecialAbility),
)
|> Ley_Option.fromOption(hero);
/**
* Get the index in the list of style dependencies where there is a free slot
* for the passed extended special ability. Prefers fixed ID slots.
*/
let getIndexOfFreeDependency =
(
extendedSpecialAbility: SpecialAbility.Static.t,
xs: list(Hero.styleDependency),
) =>
Ley_Option.Infix.
// Prefer dependency with fixed option
(
Ley_List.findIndex(
(x: Hero.styleDependency) =>
switch (x.id) {
| One(id) => id === extendedSpecialAbility.id
| Many(_) => false
},
xs,
)
// Otherwise search for dependency with dynamic option
<|> Ley_List.findIndex(
(x: Hero.styleDependency) =>
switch (x.id) {
| One(_) => false
| Many(ids) => Ley_List.elem(extendedSpecialAbility.id, ids)
},
xs,
)
|> Ley_Option.fromOption(-1)
);
/**
* Adds the passed extended special ability to a free slot of a style
* dependency.
*/
let addExtendedSpecialAbilityDependency =
(extendedSpecialAbility, hero: Hero.t) =>
extendedSpecialAbility
|> getStyleDependenciesAcc
|> Ley_Option.option(hero, ((get, set)) =>
hero
|> get
|> (
xs =>
Ley_List.Index.modifyAt(
getIndexOfFreeDependency(extendedSpecialAbility, xs),
(d: Hero.styleDependency) =>
{...d, active: Some(extendedSpecialAbility.id)},
xs,
)
|> set(hero)
)
);
/**
* Adds extended special ability dependencies if the passed entry is a style
* special ability or adds the passed special ability's ID to a free slot of a
* style dependency if it is an extended special ability.
*/
let addAllStyleRelatedDependencies = (specialAbility, hero) =>
hero
|> addStyleExtendedSpecialAbilityDependencies(specialAbility)
|> addExtendedSpecialAbilityDependency(specialAbility);
/**
* `getSplittedRemainingAndToRemove` takes a style special ability's ID and
* the list of style dependencies and returns `(toBeRemoved, remaining)`,
* where `toBeRemoved` is the list of style dependencies that have to be
* removed when removing the style special ability and `remaining` is the list
* of remaining style dependencies after the removal.
*/
let getSplittedRemainingAndToRemove = (id, xs) =>
Ley_List.partition((x: Hero.styleDependency) => x.origin === id, xs);
/**
* Returns an index of a free style dependency so that the currently used
* style dependency can be removed and the use can be moved.
*/
let getAlternativeIndex = (toBeRemoved: Hero.styleDependency, remainings) =>
switch (toBeRemoved.active) {
| Some(activeId) =>
remainings
|> Ley_List.findIndex((remaining: Hero.styleDependency) =>
(
switch (remaining.id) {
| One(id) => id === activeId
| Many(ids) => Ley_List.elem(activeId, ids)
}
)
&& Ley_Option.isNone(remaining.active)
)
|> Ley_Option.fromOption(-1)
| None => (-1)
};
/**
* Removes extended special ability dependencies if the passed entry is a
* style special ability.
*/
let removeStyleExtendedSpecialAbilityDependencies =
(styleSpecialAbility, hero) =>
styleSpecialAbility
|> getStyleDependenciesAcc
|> Ley_Option.option(hero, ((get, set)) =>
hero
|> get
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|> (
((dependenciesToRemove, remainingDependencies)) =>
dependenciesToRemove
|> Ley_List.filter((x: Hero.styleDependency) =>
Ley_Option.isSome(x.active)
)
|> Ley_List.foldr(
(toRemove: Hero.styleDependency, remainings) =>
Ley_List.Index.modifyAt(
getAlternativeIndex(toRemove, remainings),
(remaining: Hero.styleDependency) =>
{...remaining, active: toRemove.active},
remainings,
),
remainingDependencies,
)
|> set(hero)
)
);
/**
* Get the index in the list of style dependencies where there is a used slot
* for the passed extended special ability.
*/
let getIndexOfUsedDependency =
(
extendedSpecialAbility: SpecialAbility.Static.t,
xs: list(Hero.styleDependency),
) =>
Ley_List.findIndex(
(x: Hero.styleDependency) =>
switch (x.active) {
| Some(id) => id === extendedSpecialAbility.id
| None => false
},
xs,
)
|> Ley_Option.fromOption(-1);
/**
* Removes the passed extended special ability from a used slot of a style
* dependency.
*/
let removeExtendedSpecialAbilityDependency =
(extendedSpecialAbility, hero: Hero.t) =>
extendedSpecialAbility
|> getStyleDependenciesAcc
|> Ley_Option.option(hero, ((get, set)) =>
hero
|> get
|> (
xs =>
Ley_List.Index.modifyAt(
getIndexOfUsedDependency(extendedSpecialAbility, xs),
(d: Hero.styleDependency) => {...d, active: None},
xs,
)
|> set(hero)
)
);
/**
* Removes extended special ability dependencies if the passed entry is a
* style special ability or removes the passed special ability's ID from a
* used slot of a style dependency if it is an extended special ability.
*/
let removeAllStyleRelatedDependencies = (specialAbility, hero) =>
hero
|> removeStyleExtendedSpecialAbilityDependencies(specialAbility)
|> removeExtendedSpecialAbilityDependency(specialAbility);
/**
* Return flat array of available extended special abilities' IDs.
*/
let getAvailableExtendedSpecialAbilities = styleDependencies =>
styleDependencies
|> Ley_List.concatMap((x: Hero.styleDependency) =>
switch (x.active, x.id) {
| (None, One(id)) => [id]
| (None, Many(ids)) => ids
| (Some(_), _) => []
}
);
/**
* Calculates a list of available Extended Special Abilties. The availability is
* only based on bought Style Special Abilities, so (other) prerequisites have
* to be checked separately.
*/
let getAllAvailableExtendedSpecialAbilities =
Ley_List.concatMap(getAvailableExtendedSpecialAbilities);
/**
* Checks if the passed special ability is a style and if it is valid to
* remove based on registered active extended special abilities.
*/
let isStyleValidToRemove = (hero, styleSpecialAbility) =>
styleSpecialAbility
|> getStyleDependenciesAcc
|> Ley_Option.option(true, ((get, _)) =>
hero
|> get
|> getSplittedRemainingAndToRemove(styleSpecialAbility.id)
|> (
((dependenciesToRemove, remainingDependencies)) =>
dependenciesToRemove
// Check for every used dependency slot...
|> Ley_List.filter((x: Hero.styleDependency) =>
Ley_Option.isSome(x.active)
)
// ...if it can be moved to a different dependency
|> Ley_List.all(x =>
getAlternativeIndex(x, remainingDependencies) > (-1)
)
)
);
@@ -0,0 +1,37 @@
/**
* Working with Style Special Abilties and their Extended Special Abilties.
*
* This module provides some functions for working with Style and Extended
* Special Abilties, how to manage their dependencies and what is allowed to do
* in that context.
*/
/**
* Adds extended special ability dependencies if the passed entry is a style
* special ability or adds the passed special ability's ID to a free slot of a
* style dependency if it is an extended special ability.
*/
let addAllStyleRelatedDependencies:
(SpecialAbility.Static.t, Hero.t) => Hero.t;
/**
* Removes extended special ability dependencies if the passed entry is a
* style special ability or removes the passed special ability's ID from a
* used slot of a style dependency if it is an extended special ability.
*/
let removeAllStyleRelatedDependencies:
(SpecialAbility.Static.t, Hero.t) => Hero.t;
/**
* Calculates a list of available Extended Special Abilties. The availability is
* only based on bought Style Special Abilities, so (other) prerequisites have
* to be checked separately.
*/
let getAllAvailableExtendedSpecialAbilities:
list(list(Hero.styleDependency)) => list(int);
/**
* Checks if the passed special ability is a style and if it is valid to
* remove based on registered active extended special abilities.
*/
let isStyleValidToRemove: (Hero.t, SpecialAbility.Static.t) => bool;
+313
View File
@@ -0,0 +1,313 @@
module IM = Ley_IntMap;
module L = Ley_List;
module O = Ley_Option;
open Activatable_Cache;
type t = {
id: int,
name: string,
apValue: option(OneOrMany.t(int)),
minLevel: option(int),
maxLevel: option(int),
selectOptions: list(SelectOption.t),
heroEntry: option(Activatable_Dynamic.t),
staticEntry: Activatable.t,
customCostDisabled: bool,
isAutomatic: bool,
};
type result =
| Valid(t)
| Invalid(Activatable.t);
let getSermonOrVisionCountMax = (hero: Hero.t, advantageId, disadvantageId) =>
Activatable_Modifier.modifyByLevel(
3,
IM.lookup(Id.Advantage.toInt(advantageId), hero.advantages),
IM.lookup(Id.Disadvantage.toInt(disadvantageId), hero.disadvantages),
);
let modifyOtherOptions =
(cache, staticData: Static.t, hero, staticEntry, maybeHeroEntry, base) =>
switch (staticEntry) {
| Activatable.Advantage(_) => Some(base)
| Disadvantage(staticDisadvantage) =>
[@warning "-4"]
Id.Disadvantage.(
switch (fromInt(staticDisadvantage.id)) {
| WenigePredigten as id
| WenigeVisionen as id =>
Some({
...base,
maxLevel:
EntryGroups.SpecialAbility.countActiveFromGroup(
switch (id) {
| WenigePredigten => Predigten
| _ => Visionen
},
cache.specialAbilityPairs,
)
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|> O.ensure((<)(0)),
})
| SmallSpellSelection =>
Some({
...base,
maxLevel:
ActivatableSkills.countActiveSkillEntries(Spells, hero)
|> Activatable_Active_Validation.getMaxLevelForDecreaseEntry(3)
|> O.ensure((<)(0)),
})
| _ => Some(base)
}
)
| SpecialAbility(staticSpecialAbility) =>
[@warning "-4"]
Id.SpecialAbility.(
switch (fromInt(staticSpecialAbility.id)) {
| CraftInstruments =>
let getSkillValue = skill =>
hero.skills
|> IM.lookup(Id.Skill.toInt(skill))
|> Skill.Dynamic.getValueDef;
// Sum of SR of Woodworking and Metalworking must be at least 12
getSkillValue(Woodworking) + getSkillValue(Metalworking) >= 12
? Some(base) : None;
| Hunter =>
EntryGroups.CombatTechnique.getFromGroup(
Ranged,
cache.combatTechniquePairs,
)
|> IM.any(
fun
// At least one ranged combat technique must be on CtR 10 or higher
| (_, Some((heroEntry: Skill.Dynamic.t))) =>
heroEntry.value >= 10
| _ => false,
)
? Some(base) : None
| TraditionGuildMages
| TraditionWitches
| TraditionElves
| TraditionDruids
| TraditionIllusionist
| TraditionQabalyaMage
| TraditionGeoden
| TraditionZauberalchimisten
| TraditionSchelme
| TraditionBrobimGeoden =>
// Only one tradition allowed
L.null(cache.magicalTraditions) ? Some(base) : None
| PropertyKnowledge
| AspectKnowledge =>
switch (staticSpecialAbility.apValue) {
| Some(PerLevel(values)) =>
let index =
O.option(
0,
({active, _}: Activatable_Dynamic.t) => active |> L.length,
maybeHeroEntry,
);
O.Infix.(
L.Safe.atMay(values, index)
<&> (apValue => {...base, apValue: Some(One(apValue))})
);
| Some(Flat(_))
| None => None
}
| TraditionChurchOfPraios
| TraditionChurchOfRondra
| TraditionChurchOfBoron
| TraditionChurchOfHesinde
| TraditionChurchOfPhex
| TraditionChurchOfPeraine
| TraditionChurchOfEfferd
| TraditionChurchOfTravia
| TraditionChurchOfFirun
| TraditionChurchOfTsa
| TraditionChurchOfIngerimm
| TraditionChurchOfRahja
| TraditionCultOfTheNamelessOne
| TraditionChurchOfAves
| TraditionChurchOfIfirn
| TraditionChurchOfKor
| TraditionChurchOfNandus
| TraditionChurchOfSwafnir
| TraditionCultOfNuminoru =>
// Only one tradition allowed
O.isNone(cache.blessedTradition) ? Some(base) : None
| Recherchegespuer =>
O.Infix.(
hero.specialAbilities
|> IM.lookup(Id.SpecialAbility.toInt(Wissensdurst))
<&> (({active, _}) => active)
>>= O.listToOption
>>= (({options, _}) => O.listToOption(options))
>>= (
fun
| Preset(Skill(skillId)) =>
IM.lookup(skillId, staticData.skills)
| _ => None
)
>>= (
({ic, _}) =>
switch (staticSpecialAbility.apValue) {
| Some(PerLevel(values)) =>
values
|> L.map((+)(IC.getApForActivatation(ic)))
|> (
sumValues =>
{...base, apValue: Some(Many(sumValues))} |> O.return
)
| Some(Flat(_))
| None => None
}
)
)
| PredigtDerGemeinschaft
| PredigtDerZuversicht
| PredigtDesGottvertrauens
| PredigtDesWohlgefallens
| PredigtWiderMissgeschicke =>
let isAdvActive = id =>
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|> Activatable_Accessors.isActiveM;
let max =
getSermonOrVisionCountMax(
hero,
ZahlreichePredigten,
WenigePredigten,
);
let isLessThanMax =
EntryGroups.SpecialAbility.countActiveFromGroup(
Predigten,
cache.specialAbilityPairs,
)
< max;
isAdvActive(Prediger) && isLessThanMax || isAdvActive(Blessed)
? Some(base) : None;
| VisionDerBestimmung
| VisionDerEntrueckung
| VisionDerGottheit
| VisionDesSchicksals
| VisionDesWahrenGlaubens =>
let isAdvActive = id =>
IM.lookup(Id.Advantage.toInt(id), hero.advantages)
|> Activatable_Accessors.isActiveM;
let max =
getSermonOrVisionCountMax(hero, ZahlreicheVisionen, WenigeVisionen);
let isLessThanMax =
EntryGroups.SpecialAbility.countActiveFromGroup(
Visionen,
cache.specialAbilityPairs,
)
< max;
isAdvActive(Visionaer) && isLessThanMax || isAdvActive(Blessed)
? Some(base) : None;
| DunklesAbbildDerBuendnisgabe =>
O.Infix.(
hero.pact
>>= (
({level, _}) =>
level > 0 ? Some({...base, maxLevel: Some(level)}) : None
)
)
| TraditionArcaneBard
| TraditionArcaneDancer
| TraditionIntuitiveMage
| TraditionSavant
| TraditionAnimisten =>
cache.adventurePoints.spentOnMagicalAdvantages <= 25
&& cache.adventurePoints.spentOnMagicalDisadvantages <= 25
&& L.null(cache.magicalTraditions)
? Some(base) : None
| _ => Some(base)
}
)
};
let getInactive = (cache, staticData, hero, staticEntry, maybeHeroEntry) => {
let maxLevel =
Activatable_Inactive_Validation.getMaxLevel(
staticData,
hero,
staticEntry,
maybeHeroEntry,
);
let isAdditionValid =
Activatable_Inactive_Validation.isAdditionValid(
cache,
staticData,
hero,
maxLevel,
staticEntry,
maybeHeroEntry,
);
if (isAdditionValid) {
O.Infix.(
Activatable_Inactive_SelectOptions.getAvailableSelectOptions(
staticData,
hero,
cache.magicalTraditions,
staticEntry,
maybeHeroEntry,
)
<&> (
selectOptions => {
id: Activatable_Accessors.id'(staticEntry),
name: Activatable_Accessors.name(staticEntry),
apValue: Activatable_Accessors.apValue'(staticEntry),
minLevel: None,
maxLevel,
selectOptions,
heroEntry: maybeHeroEntry,
staticEntry,
customCostDisabled: true,
isAutomatic:
switch (staticEntry) {
| Advantage({id, _}) => L.elem(id, cache.automaticAdvantages)
| Disadvantage(_)
| SpecialAbility(_) => false
},
}
)
>>= (
base =>
modifyOtherOptions(
cache,
staticData,
hero,
staticEntry,
maybeHeroEntry,
base,
)
)
|> O.option(Invalid(staticEntry), base => Valid(base))
);
} else {
Invalid(staticEntry);
};
};
+34
View File
@@ -0,0 +1,34 @@
type t = {
id: int,
name: string,
apValue: option(OneOrMany.t(int)),
minLevel: option(int),
maxLevel: option(int),
selectOptions: list(SelectOption.t),
heroEntry: option(Activatable_Dynamic.t),
staticEntry: Activatable.t,
customCostDisabled: bool,
isAutomatic: bool,
};
type result =
| Valid(t)
| Invalid(Activatable.t);
/**
* `getInactive cache staticData hero staticEntry maybeHeroEntry` validates an
* inactive entry, filters possible options and then either returns a valid
* entry with additional and filtered options or an invalid entry.
*
* This can be used to display valid as well as invalid entries in a single
* list.
*/
let getInactive:
(
Activatable_Cache.t,
Static.t,
Hero.t,
Activatable.t,
option(Activatable_Dynamic.t)
) =>
result;
@@ -0,0 +1,699 @@
open SelectOption;
open Ley_Option;
open Activatable_Accessors;
open Activatable_SelectOptions;
module F = Ley_Function;
module SO = SelectOption;
module SOM = SelectOption.Map;
module L = Ley_List;
module O = Ley_Option;
module T = Ley_Transducer;
module IM = Ley_IntMap;
module IS = Ley_IntSet;
open T;
/**
* Test if the id of the passed select option is activated for the passed
* `ActivatableDependent`.
*/
let isNotActive = maybeHeroEntry =>
maybeHeroEntry
|> O.option([], getActiveSelectOptions1)
|> ((activeSelections, {id, _}: SO.t) => L.notElem(id, activeSelections));
/**
* Test if a select option is not activated more than once for the passed
* `ActivatableDependent`.
*/
let areNoSameActive = maybeHeroEntry =>
maybeHeroEntry
|> O.option([], getActiveSelectOptions1)
|> (
(activeSelections, {id, _}: SO.t) => L.countMax(id, 1, activeSelections)
);
/**
* `isNotRequired otherActivatables maybeHeroEntry so` returns if the id of the
* passed select option `so` is required for the passed Activatable
* `maybeHeroEntry`. `otherActivatables` should be a map of the Activatables
* from the same group ()
*/
let isNotRequired = (otherActivatables, maybeHeroEntry) =>
maybeHeroEntry
|> O.option(
[],
Dependencies.Flatten.getRequiredSelectOptions1(otherActivatables),
)
|> (
(applicableOptions, {id, _}: SO.t) =>
L.all(
fun
| OneOrMany.One(option) => Id.Activatable.SelectOption.(option != id)
| Many(options) => L.notElem(id, options),
applicableOptions,
)
);
/**
* `isNotRequiredNotActive :: Maybe ActivatableDependent -> SelectOption -> Bool`
*
* Test if the id of the passed select option is neither required nor activated
* for the passed `ActivatableDependent`.
*/
let isNotRequiredNotActive = (otherActivatables, maybeHeroEntry) => {
let isNoActiveSelection = isNotActive(maybeHeroEntry);
let isNoRequiredSelection =
isNotRequired(otherActivatables, maybeHeroEntry);
so => isNoActiveSelection(so) && isNoRequiredSelection(so);
};
/**
* Returns a transducer to check if an item is available and if their
* prerequisites are met.
*/
let isValid = (staticData: Static.t, hero: Hero.t, entryId, fold) =>
fold
>>~ EntryAvailability.isAvailableNull(
(x: SelectOption.t) => x.src,
staticData.publications,
hero.rules,
)
>>~ (
x =>
x.prerequisites
|> Prerequisite.Collection.ByLevel.getFirstLevel
|> Ley_List.map(Prerequisite.General.unify)
|> Prerequisites.Validation.arePrerequisitesMet(
staticData,
hero,
entryId,
)
);
/**
* Returns a transducer to check if an item is available, if their
* prerequisites are met, and if its neither active nor required.
*/
let isNoGenericRequiredOrActiveSelection =
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
fold
|> isValid(staticData, hero, entryId)
>>~ isNotRequiredNotActive(otherActivatables, maybeHeroEntry);
/**
* Returns a transducer to check if an item is available, if their
* prerequisites are met, and if its not required.
*/
let isNoGenericRequiredSelection =
(staticData, hero, maybeHeroEntry, otherActivatables, entryId, fold) =>
fold
|> isValid(staticData, hero, entryId)
>>~ isNotRequired(otherActivatables, maybeHeroEntry);
/**
* From a list of spell select options, only return the ones that match the
* predicate.
*/
let filterSpellSelectOptions = (pred, fold) =>
fold
>>~ (
(x: SelectOption.t) =>
[@warning "-4"]
(
switch (x.staticEntry) {
| Some(Spell(spell)) => Some(spell)
| _ => None
}
)
|> O.option(false, pred)
);
let getAvailableSelectOptionsTransducer =
(
staticData: Static.t,
hero: Hero.t,
magicalTraditions,
staticEntry,
maybeHeroEntry,
) => {
// Prefill some parameters
let isValidShort = isValid(staticData, hero);
// Prefill some parameters
let isNoGenericRequiredOrActiveSelectionShort =
isNoGenericRequiredOrActiveSelection(staticData, hero, maybeHeroEntry);
// Prefill some parameters
let isNoGenericRequiredSelectionShort =
isNoGenericRequiredSelection(staticData, hero, maybeHeroEntry);
switch ((staticEntry: Activatable.t)) {
| Advantage(staticAdvantage) =>
let id = Id.All.Advantage(staticAdvantage.id);
let isNoRequiredOrActiveSelection =
isNoGenericRequiredOrActiveSelectionShort(hero.advantages, id);
let isNoRequiredSelection =
isNoGenericRequiredSelectionShort(hero.advantages, id);
[@warning "-4"]
(
switch (Id.Advantage.fromInt(staticAdvantage.id)) {
| ExceptionalSkill =>
Some(
fold =>
// There shouldnt be more than two activations for the same skill
fold |> isNoRequiredSelection >>~ areNoSameActive(maybeHeroEntry),
)
| _ => Some(isNoRequiredOrActiveSelection)
}
);
| Disadvantage(staticDisadvantage) =>
let id = Id.All.Disadvantage(staticDisadvantage.id);
let isNoRequiredOrActiveSelection =
isNoGenericRequiredOrActiveSelectionShort(hero.disadvantages, id);
[@warning "-4"]
(
switch (Id.Disadvantage.fromInt(staticDisadvantage.id)) {
| PersonalityFlaw =>
// List of unique active select options
let uniqueSelections =
O.option(
[],
heroEntry =>
heroEntry
|> mapActiveOptions1(
fun
| Preset(Generic(id)) => Some(id)
| _ => None,
),
maybeHeroEntry,
);
// Checks if the passed id either matches the passed select option's id
// and is already active.
let isInfiniteActive = (id, selectOption: SO.t) =>
selectOption.id == Generic(id) && L.elem(id, uniqueSelections);
// Checks if the passed select option is Prejudice and if Prejudice is
// already active.
let isPrejudiceAndActive = isInfiniteActive(7);
// Checks if the passed select option is Unworldly and if Unworldly is
// already active.
let isUnworldlyAndActive = isInfiniteActive(8);
// Checks if the passed select option is not active, not required and if
// there is a maximum of one other unique select option of this entry
// active.
let isNotActiveAndMaxNotReached = selectOption =>
isNotRequiredNotActive(
hero.disadvantages,
maybeHeroEntry,
selectOption,
)
&& L.lengthMax(1, uniqueSelections);
Some(
fold =>
fold
|> isValidShort(Disadvantage(staticDisadvantage.id))
>>~ (
x =>
// If Prejudice is active, it can be activated multiple times
isPrejudiceAndActive(x)
// If Unworldly is active, it can be activated multiple times
|| isUnworldlyAndActive(x)
// If the select option has not been activated yet, there must be
// a maximum of one other unique activations, since only a maximum
// of two unique is allowed
|| isNotActiveAndMaxNotReached(x)
),
);
| NegativeTrait
| Maimed => Some(isNoRequiredOrActiveSelection)
| Incompetent =>
// Checks if the advantage with the passed id is active
let isAdvActive = id =>
IM.lookup(Id.Advantage.toInt(id), hero.advantages) |> isActiveM;
// Checks if the passed select option does not represent a social skill
let isNotSocialSkill = (so: SO.t) =>
switch (so.id) {
| Skill(id) =>
IM.lookup(id, staticData.skills)
|> O.any((skill: Skill.Static.t) =>
skill.gr === Id.Skill.Group.toInt(Social)
)
|> (!)
| _ => true
};
Some(
fold =>
fold
|> isNoRequiredOrActiveSelection
>>~ (
x =>
// Socially Adaptable and Inspire Confidence
// require no Incompetence in social skills
isAdvActive(SociallyAdaptable)
|| isAdvActive(InspireConfidence)
? isNotSocialSkill(x) : true
),
);
| _ => Some(isNoRequiredOrActiveSelection)
}
);
| SpecialAbility(staticSpecialAbility) =>
let id = Id.All.SpecialAbility(staticSpecialAbility.id);
let isNoRequiredOrActiveSelection =
isNoGenericRequiredOrActiveSelectionShort(hero.specialAbilities, id);
let isNoRequiredSelection =
isNoGenericRequiredSelectionShort(hero.specialAbilities, id);
[@warning "-4"]
(
switch (Id.SpecialAbility.fromInt(staticSpecialAbility.id)) {
| SkillSpecialization =>
let isFirstSpecializationValid = id =>
IM.lookup(id, hero.skills)
|> O.option(false, (skill: Skill.Dynamic.t)
// Skill Rating must be at least 6 for 1st specialization
=> skill.value >= 6);
let isAdditionalSpecializationValid =
(counter, selectOption: SO.t, id) =>
liftM2(
(skill: Skill.Dynamic.t, activeApps) =>
// Maximum of three is allowed
L.lengthMax(2, activeApps)
// Skill Rating must be at least 6/12/18 for 1st/2nd/3rd specialization
&& skill.value >= (L.length(activeApps) + 1)
* 6,
IM.lookup(id, hero.skills),
SOM.lookup(selectOption.id, counter),
)
|> O.dis;
let counter = O.Infix.(maybeHeroEntry <&> getActiveOptions2Map);
Some(
fold =>
fold
|> isNoRequiredSelection
>>~ (
switch (counter) {
// if counter is available, maybeHeroEntry must be a Some and thus there
// can be active selections
| Some(counter) => (
selectOption =>
switch (selectOption.id) {
| Skill(id) =>
SOM.member(selectOption.id, counter)
? isAdditionalSpecializationValid(
counter,
selectOption,
id,
)
: isFirstSpecializationValid(id)
| _ => true
}
)
// otherwise we only need to check if the skill rating is at least 6, as
// there can't be an activated selection then.
| None => (
selectOption =>
switch (selectOption.id) {
| Skill(id) => isFirstSpecializationValid(id)
| _ => true
}
)
}
)
<&~ (
(selectOption: SO.t) => {
let maybeCurrentCount =
O.Infix.(counter >>= SOM.lookup(selectOption.id));
selectOption
// Increase cost if there are active specializations
// for the same skill
|> O.liftDef(selectOption =>
O.liftM2(
(currentCount, selectOptionCost) =>
{
...selectOption,
apValue:
Some(
selectOptionCost * (L.length(currentCount) + 1),
),
},
maybeCurrentCount,
selectOption.apValue,
)
)
|> O.liftDef((selectOption: SO.t) =>
Ley_Option.Infix.(
(
switch (selectOption.staticEntry) {
| Some(Skill(skill)) => Some(skill)
| _ => None
}
)
<&> (
skill =>
skill.applications
|> IM.elems
|> L.filter(
(application: Skill.Static.Application.t) => {
// An application can only be selected once
let isInactive =
O.all(
L.notElem(
Id.Activatable.Option.Preset(
Generic(application.id),
),
),
maybeCurrentCount,
);
// New applications must have valid prerequisites
let hasValidPrerequisites =
Prerequisites.Validation.arePrerequisitesMet(
staticData,
hero,
id,
switch (application.prerequisite) {
| Some(prerequisite) => [
{
value: Activatable(prerequisite),
displayOption: Generate,
},
]
| None => []
},
);
isInactive && hasValidPrerequisites;
})
|> (
applications => {
...selectOption,
applications: Some(applications),
}
)
)
)
);
}
),
);
| TraditionGuildMages =>
// Filter all spells so that only spells are included that cant be
// familiar spells
Some(
filterSpellSelectOptions(spell =>
Ley_IntSet.disjoint(
spell.traditions,
Ley_IntSet.fromList([
Id.MagicalTradition.toInt(General),
Id.MagicalTradition.toInt(GuildMages),
]),
)
),
)
| PropertyKnowledge as id
| AspectKnowledge as id =>
let availableIds =
switch (id) {
| PropertyKnowledge =>
Spells.PropertyKnowledge.getAvailableProperties(
staticData.spells,
hero.spells,
)
| AspectKnowledge =>
LiturgicalChants.AspectKnowledge.getAvailableAspects(
staticData.liturgicalChants,
hero.liturgicalChants,
)
// Never happens
| _ => []
};
let isValidId = (selectOption: SO.t) =>
switch (selectOption.id) {
| Generic(id) => L.elem(id, availableIds)
| _ => false
};
Some(fold => fold |> isNoRequiredOrActiveSelection >>~ isValidId);
| PropertyFocus =>
let activePropertyKnowledges =
maybeHeroEntry |> O.option([], getActiveSelectOptions1);
let hasActivePropertyKnowledge = (selectOption: SO.t) =>
switch (selectOption.id) {
| Generic(_) as id => L.elem(id, activePropertyKnowledges)
| _ => false
};
Some(
fold =>
fold
|> isNoRequiredOrActiveSelection
>>~ hasActivePropertyKnowledge,
);
| AdaptionZauber =>
let minimumSkillRating = 10;
let hasSpellMinimumSr = (selectOption: SO.t) =>
switch (selectOption.id) {
| Spell(id) =>
IM.lookup(id, hero.spells)
|> O.option(false, (spell: ActivatableSkill.Dynamic.t) =>
switch (spell.value) {
| Active(value) => value >= minimumSkillRating
| Inactive => false
}
)
| _ => false
};
let isUnfamiliarSpell = (selectOption: SO.t) =>
switch (selectOption.id) {
| Spell(id) =>
IM.lookup(id, staticData.spells)
|> O.option(
false,
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
hero.transferredUnfamiliarSpells,
magicalTraditions,
),
)
| _ => false
};
Some(
fold =>
fold
|> isNoRequiredOrActiveSelection
>>~ hasSpellMinimumSr
>>~ isUnfamiliarSpell,
);
| SpellEnhancement as id
| ChantEnhancement as id =>
let getTargetHeroEntry =
switch (id) {
| SpellEnhancement => (id => IM.lookup(id, hero.spells))
| ChantEnhancement => (id => IM.lookup(id, hero.liturgicalChants))
| _ => (_ => None)
};
let isNotUnfamiliar =
Ley_Bool.notP(
Dependencies.TransferredUnfamiliar.isUnfamiliarSpell(
hero.transferredUnfamiliarSpells,
magicalTraditions,
),
);
let isValueValid = (maybeLevel, heroEntry: ActivatableSkill.Dynamic.t) =>
switch (maybeLevel, heroEntry.value) {
| (Some(level), Active(value)) => value >= level * 4 + 4
| (None, Active(_))
| (None, Inactive)
| (Some(_), Inactive) => false
};
Some(
fold =>
fold
|> isNoRequiredOrActiveSelection
>>~ (
(selectOption: SO.t) =>
switch (
selectOption.staticEntry,
O.Infix.(
selectOption.enhancementTarget >>= getTargetHeroEntry
),
) {
| (Some(Spell(staticSpell)), Some(heroSpell)) =>
isNotUnfamiliar(staticSpell)
&& isValueValid(selectOption.enhancementLevel, heroSpell)
| (Some(LiturgicalChant(_)), Some(heroChant)) =>
isValueValid(selectOption.enhancementLevel, heroChant)
| _ => false
}
),
);
| LanguageSpecializations =>
// Pair: fst = sid, snd = current_level
let availableLanguages =
hero.specialAbilities
|> IM.lookup(Id.SpecialAbility.toInt(Language))
|> O.option([], (heroLanguage: Activatable_Dynamic.t) =>
heroLanguage.active
|> O.mapOption((active: Activatable_Dynamic.single) =>
O.Infix.(
active.level
>>= (
level =>
switch (level, active.options |> O.listToOption) {
| (3 | 4, Some(Preset(Generic(sid)))) =>
Some((sid, level))
| _ => None
}
)
)
)
);
Some(
fold =>
fold
|> isNoRequiredOrActiveSelection
|> mapOptionT((selectOption: SO.t) =>
switch (selectOption.id) {
| Generic(sid) =>
switch (L.lookup(sid, availableLanguages)) {
| Some(4) => Some({...selectOption, apValue: Some(0)})
| Some(3) => Some(selectOption)
| Some(_)
| None => None
}
| _ => None
}
),
);
| MadaschwesternStil =>
Some(
filterSpellSelectOptions(spell =>
Ley_IntSet.disjoint(
spell.traditions,
Ley_IntSet.fromList([
Id.MagicalTradition.toInt(General),
Id.MagicalTradition.toInt(Witches),
]),
)
),
)
| ScholarDesMagierkollegsZuHoningen =>
let allowedTraditions =
[Id.MagicalTradition.Druids, Elves, Witches]
|> L.map(Id.MagicalTradition.toInt)
|> IS.fromList;
let maybeTransferredSpellFromTradition =
O.Infix.(
hero.specialAbilities
|> IM.lookup(Id.SpecialAbility.toInt(TraditionGuildMages))
>>= (
specialAbility =>
specialAbility.active
|> O.listToOption
>>= (
active =>
(
switch (active.options |> O.listToOption) {
| Some(Preset(Spell(id))) => Some(id)
| Some(_)
| None => None
}
)
>>= F.flip(IM.lookup, staticData.spells)
<&> (spell => spell.traditions)
)
)
);
maybeTransferredSpellFromTradition
|> O.option(
idT,
transferredSpellFromTradition => {
// Contains all allowed trads the first spell does not have
let traditionDiff =
IS.Infix.(allowedTraditions \/ transferredSpellFromTradition);
let hasTransferredSpellAllAllowedTraditions =
IS.null(traditionDiff);
filterSpellSelectOptions(spell =>
Ley_IntSet.notElem(
Id.MagicalTradition.toInt(General),
spell.traditions,
)
&& Ley_IntSet.any(
F.flip(
IS.elem,
hasTransferredSpellAllAllowedTraditions
? allowedTraditions : traditionDiff,
),
spell.traditions,
)
);
},
)
|> O.return;
| _ => Some(isNoRequiredOrActiveSelection)
}
);
};
};
let getAvailableSelectOptions =
(
staticData: Static.t,
hero: Hero.t,
magicalTraditions,
staticEntry,
maybeHeroEntry,
) => {
let allSelectOptions =
Activatable_Accessors.selectOptions(staticEntry) |> SO.Map.elems;
getAvailableSelectOptionsTransducer(
staticData,
hero,
magicalTraditions,
staticEntry,
maybeHeroEntry,
)
|> (
fun
| Some(transducer) =>
switch (transduceList(transducer, allSelectOptions)) {
| [] => L.null(allSelectOptions) ? Some([]) : None
| xs => Some(xs)
}
| None => Some(allSelectOptions)
);
};
@@ -0,0 +1,16 @@
/**
* `getAvailableSelectOptions static hero magicalTraditions staticEntry
* heroEntry` returns a list of possible select options for an inactive
* activatable entry. Note: If `None` is returned, no select option is valid and
* thus the entry cannot be added at all. This is different to a returned empty
* list, which can happen if the entry just has no select options at all.
*/
let getAvailableSelectOptions:
(
Static.t,
Hero.t,
list(Tradition.Magical.fullTradition),
Activatable.t,
option(Activatable_Dynamic.t)
) =>
option(list(SelectOption.t));
@@ -0,0 +1,245 @@
module F = Ley_Function;
module I = Ley_Int;
module IM = Ley_IntMap;
module L = Ley_List;
module O = Ley_Option;
open Activatable_Cache;
let getActivePactGiftsCount = specialAbilityPairs =>
specialAbilityPairs
|> EntryGroups.SpecialAbility.getFromGroup(Paktgeschenke)
|> IM.foldr(
fun
| (
{SpecialAbility.Static.prerequisites, apValue, levels, _},
Some(({active, _}: Activatable_Dynamic.t) as heroEntry),
)
when Activatable_Accessors.isActive(heroEntry) =>
[@warning "-4"]
(
switch (prerequisites, apValue, levels, active) {
| (
// It must have different prerequisites for each level
ByLevel(_),
// It must have different cost per level
Some(PerLevel(_)),
// It must have levels
Some(_),
// The current level must be set
[{level: Some(level), _}, ..._],
) =>
// Then each level counts as one activated pact present
(+)(level)
| _ => (+)(1)
}
)
| _ => F.id,
0,
);
let isSpecialAbilitySpecificAdditionEnabled =
(
~rules: Hero.Rules.t,
~maybePact,
{
combatStyleCombination,
armedCombatStylesCount,
unarmedCombatStylesCount,
magicalStyleCombination,
magicalStylesCount,
isBlessedStyleActive,
dunklesAbbild,
activePactGiftsCount,
matchingLanguagesScripts,
_,
},
specialAbility: SpecialAbility.Static.t,
) =>
[@warning "-4"]
Id.SpecialAbility.(
switch (fromInt(specialAbility.id), Group.fromInt(specialAbility.gr)) {
| (_, CombatStylesArmed as gr | CombatStylesUnarmed as gr) =>
let equalTypeStylesActive =
switch (gr) {
| CombatStylesArmed => armedCombatStylesCount
| CombatStylesUnarmed => unarmedCombatStylesCount
| _ => 0
};
// Combination-SA is active, which allows 3 styles to be active, but only a
// maximum of 2 from one type (armed/unarmed).
if (Activatable_Accessors.isActiveM(combatStyleCombination)) {
let totalActive = armedCombatStylesCount + unarmedCombatStylesCount;
totalActive < 3 || equalTypeStylesActive < 2;
} else {
// Otherwise, only one of each type can be active.
equalTypeStylesActive === 0;
};
| (_, MagicalStyles) =>
magicalStylesCount
< (Activatable_Accessors.isActiveM(magicalStyleCombination) ? 2 : 1)
| (_, BlessedStyles) => !isBlessedStyleActive
| (CombatStyleCombination, _) =>
armedCombatStylesCount + unarmedCombatStylesCount > 0
| (MagicStyleCombination, _) => magicalStylesCount > 0
| (DunklesAbbildDerBuendnisgabe, _) => activePactGiftsCount === 0
| (WegDerSchreiberin, _) =>
L.length(matchingLanguagesScripts.languagesWithMatchingScripts) >= 1
&& L.length(matchingLanguagesScripts.scriptsWithMatchingLanguages) >= 1
| (_, Paktgeschenke) =>
switch (maybePact) {
| Some((pact: Pact.Dynamic.t)) =>
switch (Id.Pact.fromInt(pact.category)) {
| Faery =>
!Activatable_Accessors.isActiveM(dunklesAbbild)
&& pact.level > activePactGiftsCount
| Demon =>
// Minor Pact
pact.level <= 0
// Minor Pact only allows up to 3 pact gifts
? activePactGiftsCount < 3
// "Normal" Pact allows up to CoD + 7 pact gifts
: pact.level + 7 > activePactGiftsCount
| Other(_) => false
}
| None => false
}
| (LanguageSpecializations, _) =>
IM.member(
Id.OptionalRule.toInt(LanguageSpecialization),
rules.activeOptionalRules,
)
| (_, Vampirismus | Lykanthropie) =>
// TODO: add option to activate vampire or lycanthropy and activate this
// SAs based on that option
false
| _ => true
}
);
let isEntrySpecificAdditionEnabled =
(cache, staticData, hero: Hero.t, staticEntry) =>
(
switch (staticEntry) {
| Activatable.Advantage(_)
| Disadvantage(_) => true
| SpecialAbility(staticSpecialAbility) =>
isSpecialAbilitySpecificAdditionEnabled(
~rules=hero.rules,
~maybePact=hero.pact,
cache,
staticSpecialAbility,
)
}
)
&& Prerequisites.Validation.arePrerequisitesMet(
staticData,
hero,
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
Prerequisites.Activatable.getFlatFirstPrerequisites(staticEntry),
);
let hasNoGenerallyRestrictingDependency =
fun
| Some({Activatable_Dynamic.dependencies, _}) =>
L.all(
[@warning "-4"]
(
fun
| {
Activatable_Dynamic.target: One(_),
options: [],
level: None,
active: false,
_,
} =>
false
| _ => true
),
dependencies,
)
| _ => true;
let hasNotReachedMaximumEntries = (maxLevel, maybeHeroEntry) =>
switch (maxLevel, maybeHeroEntry) {
// Impossible maximum level, thus no addition possible
| (Some(0), _) => false
| (Some(max), Some(({active, _}: Activatable_Dynamic.t))) =>
max > L.length(active)
| (Some(_), None)
| (None, _) => true
};
let isValidExtendedSpecialAbility =
({validExtendedSpecialAbilities, _}, staticEntry) =>
switch (staticEntry) {
| Activatable.Advantage(_)
| Disadvantage(_) => true
| SpecialAbility(staticSpecialAbility) =>
[@warning "-4"]
Id.SpecialAbility.Group.(
switch (fromInt(staticSpecialAbility.gr)) {
| CombatExtended
| MagicalExtended
| KarmaExtended
| SkillExtended =>
L.elem(staticSpecialAbility.id, validExtendedSpecialAbilities)
| _ => true
}
)
};
let appliesToMagicalActionsIfRequired =
({requiredApplyToMagicalActions, _}, staticEntry) =>
switch (staticEntry) {
| Activatable.Advantage(staticAdvantage) =>
!requiredApplyToMagicalActions
|| !staticAdvantage.isExclusiveToArcaneSpellworks
| Disadvantage(staticDisadvantage) =>
!requiredApplyToMagicalActions
|| !staticDisadvantage.isExclusiveToArcaneSpellworks
| SpecialAbility(_) => true
};
let isAdditionValid =
(cache, staticData, hero, maxLevel, staticEntry, maybeHeroEntry) =>
isEntrySpecificAdditionEnabled(cache, staticData, hero, staticEntry)
&& hasNoGenerallyRestrictingDependency(maybeHeroEntry)
&& hasNotReachedMaximumEntries(maxLevel, maybeHeroEntry)
&& isValidExtendedSpecialAbility(cache, staticEntry)
&& appliesToMagicalActionsIfRequired(cache, staticEntry);
let getMaxLevel = (staticData, hero, staticEntry, maybeHeroEntry) =>
staticEntry
|> Prerequisites.Activatable.getLevelPrerequisites
|> Dependencies.getMaxLevel(
staticData,
hero,
Id.Activatable.toAll(Activatable_Accessors.id(staticEntry)),
O.option(
[],
({Activatable_Dynamic.dependencies, _}) => dependencies,
maybeHeroEntry,
),
)
|> (
computedMax =>
switch (computedMax, Activatable_Accessors.max(staticEntry)) {
| (Some(max1), Some(max2)) => Some(I.min(max1, max2))
| (Some(max), None)
| (None, Some(max)) => Some(max)
| (None, None) => None
}
);
@@ -0,0 +1,42 @@
/**
* Validate addition of inactive entries
*
* This module provides functions to determine if an (inactive) entry is allowed
* to be bought/added.
*/
/**
* `getActivePactGiftsCount pairs` returns the amount of active pact gifts based
* on the given set of static/hero special ability entry pairs.
*/
let getActivePactGiftsCount:
Ley_IntMap.t((SpecialAbility.Static.t, option(Activatable_Dynamic.t))) =>
int;
/**
* `isAdditionValid cache staticData hero maybeMaxLevel staticEntry
* maybeHeroEntry` checks whether an activatable entry can be added/bought. The
* `cache` contains computed values that are used for multiple entries so they
* can be reused and thus improve performance for long lists of entries.
*/
let isAdditionValid:
(
Activatable_Cache.t,
Static.t,
Hero.t,
option(int),
Activatable.t,
option(Activatable_Dynamic.t)
) =>
bool;
/**
* `getMaxLevel staticData staticEntry maybeHeroEntry` returns the maximum level based on the level prerequisites (excluding first level prerequisites) where the key is the level and the value the prerequisites that need to be met for the respective level as well as based on the registered `dependencies` of the entry.
*
* The return value is based on prerequisites *and* entry dependencies. To get
* the max level only based on prerequisites, use
* `Prerequisites.Validation.getMaxLevel`.
*/
let getMaxLevel:
(Static.t, Hero.t, Activatable.t, option(Activatable_Dynamic.t)) =>
option(int);
+58
View File
@@ -0,0 +1,58 @@
module L = Ley_List;
module O = Ley_Option;
let getLevel = (maybeEntry: option(Activatable_Dynamic.t)) =>
O.Infix.(
maybeEntry
>>= (entry => entry.active |> O.listToOption)
>>= (active => active.level)
|> O.sum
);
/**
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
* the passed entries. The `inc` entry's level increases the value while the
* `dec` entry's level decreases the value.
*/
let modifyByLevel = (value, inc, dec) =>
value + getLevel(inc) - getLevel(dec);
/**
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
* the passed entries. The `inc` entry's level increases the value while the
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
* function always returns `0`.
*/
let modifyByLevelM = (value, inc, dec) =>
O.option(0, value => modifyByLevel(value, inc, dec), value);
/**
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
* the entry `inc`, that should increase the base, is active, it adds `1` to the
* base. If the entry `dec`, that should decrease the base, is active, it
* subtracts `1` from the base. If the passed base is `Nothing`, this function
* always returns `0`.
*/
let getModifierByIsActive = (value, inc, dec) =>
O.option(
0,
value =>
Activatable_Accessors.isActiveM(inc)
? value + 1 : Activatable_Accessors.isActiveM(dec) ? value - 1 : value,
value,
);
/**
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
* summing all active entries that should increase the base (`incs`) and all
* active entries that should decrease the base (`decs`).
*/
let getModifierByIsActives = (value, incs, decs) =>
O.option(
0,
value =>
value
+ L.countBy(Activatable_Accessors.isActiveM, incs)
- L.countBy(Activatable_Accessors.isActiveM, decs),
value,
);
+54
View File
@@ -0,0 +1,54 @@
/**
* `modifyByLevel value inc dec` modifies a base `value` by the active level of
* the passed entries. The `inc` entry's level increases the value while the
* `dec` entry's level decreases the value.
*/
let modifyByLevel:
(
int,
option(Activatable_Dynamic.t('a)),
option(Activatable_Dynamic.t('a))
) =>
int;
/**
* `modifyByLevelM value inc dec` modifies a base `value` by the active level of
* the passed entries. The `inc` entry's level increases the value while the
* `dec` entry's level decreases the value. If `value` is `Nothing`, this
* function always returns `0`.
*/
let modifyByLevelM:
(
option(int),
option(Activatable_Dynamic.t('a)),
option(Activatable_Dynamic.t('a))
) =>
int;
/**
* `getModifierByActiveLevel value inc dec` adjusts the given base `value`. If
* the entry `inc`, that should increase the base, is active, it adds `1` to the
* base. If the entry `dec`, that should decrease the base, is active, it
* subtracts `1` from the base. If the passed base is `Nothing`, this function
* always returns `0`.
*/
let getModifierByIsActive:
(
option(int),
option(Activatable_Dynamic.t('a)),
option(Activatable_Dynamic.t('a))
) =>
int;
/**
* `getModifierByActiveLevels value incs decs` adjusts the given base `value` by
* summing all active entries that should increase the base (`incs`) and all
* active entries that should decrease the base (`decs`).
*/
let getModifierByIsActives:
(
option(int),
list(option(Activatable_Dynamic.t('a))),
list(option(Activatable_Dynamic.t('a)))
) =>
int;
@@ -0,0 +1,130 @@
open SelectOption;
open Ley_Option.Infix;
open Activatable_Convert;
open Activatable_Accessors;
module L = Ley_List;
module O = Ley_Option;
module SOM = SelectOption.Map;
module F = Ley_Function;
let getSelectOption = (x, id) => Map.lookup(id, selectOptions(x));
/**
* Get a selection option's name with the given id from given wiki entry.
* Returns `None` if not found.
*/
let getSelectOptionName = (x, id) =>
id |> getSelectOption(x) <&> (y => y.name);
/**
* Get a selection option's cost with the given id from given wiki entry.
* Returns `None` if not found.
*/
let getSelectOptionCost = (x, id) =>
id |> getSelectOption(x) >>= (y => y.apValue);
/**
* Get all option IDs from the given entry at the passed index.
*/
let getActiveOptions = (index, x: Activatable_Dynamic.t) =>
x.active
|> O.mapOption((y: Activatable_Dynamic.single) =>
Ley_List.Safe.atMay(y.options, index)
);
let mapActiveOptions = (f, index, x: Activatable_Dynamic.t) =>
x.active
|> O.mapOption((y: Activatable_Dynamic.single) =>
Ley_List.Safe.atMay(y.options, index) >>= f
);
/**
* Get all first option IDs from the given entry.
*/
let getActiveOptions1 = (x: Activatable_Dynamic.t) =>
x.active
|> O.mapOption((y: Activatable_Dynamic.single) =>
y.options |> O.listToOption
);
let mapActiveOptions1 = (f, x: Activatable_Dynamic.t) =>
x.active
|> O.mapOption((y: Activatable_Dynamic.single) =>
y.options |> O.listToOption >>= f
);
/**
* Get all first select option IDs from the given entry.
*/
let getActiveSelectOptions1 =
mapActiveOptions1(Activatable_Convert.activatableOptionToSelectOptionId);
/**
* Get all second option IDs from the given entry.
*/
let getActiveOptions2 = getActiveOptions(1);
/**
* Get all second option ids from the given entry, sorted by their first option
* id in a map.
*/
let getActiveOptions2Map = (x: Activatable_Dynamic.t) =>
L.foldr(
(current: Activatable_Dynamic.single, mp) =>
current.options
|> O.listToOption
>>= Activatable_Convert.activatableOptionToSelectOptionId
|> O.liftM2(
(secondOption, option) =>
SOM.alter(
maybeSecondOptions =>
maybeSecondOptions
|> O.fromOption([])
|> L.cons(secondOption)
|> (xs => Some(xs)),
option,
mp,
),
current.options |> F.flip(L.Safe.atMay, 1),
)
|> O.fromOption(mp),
SOM.empty,
x.active,
);
let getOption = (index, heroEntry) =>
Ley_List.Safe.atMay(heroEntry.options, index);
let getOption1 = heroEntry => heroEntry.options |> Ley_Option.listToOption;
let getOption2 = getOption(1);
let getOption3 = getOption(2);
let getCustomInput = (option: Id.Activatable.Option.t) =>
switch (option) {
| CustomInput(x) => Some(x)
| Preset(_) => None
};
let getGenericId = (option: Id.Activatable.Option.t) =>
[@warning "-4"]
(
switch (option) {
| Preset(Generic(x)) => Some(x)
| _ => None
}
);
let lookupMap = (k, mp, f) => f <$> Ley_IntMap.lookup(k, mp);
let getSkillFromOption =
(staticData: Static.t, option: Id.Activatable.Option.t) =>
[@warning "-4"]
(
switch (option) {
| Preset(Skill(id)) => Ley_IntMap.lookup(id, staticData.skills)
| _ => None
}
);
@@ -0,0 +1,77 @@
/**
* Get specific Select Options.
*
* This module provides some utility functions for working with static and
* active Select Options.
*/
open Activatable_Convert;
/**
* Get a select option with the given id from given static entry. Returns
* `Nothing` if not found.
*/
let getSelectOption:
(Activatable.t, Id.Activatable.SelectOption.t) => option(SelectOption.t);
/**
* Get a select option's name with the given id from given static entry.
* Returns `Nothing` if not found.
*/
let getSelectOptionName:
(Activatable.t, Id.Activatable.SelectOption.t) => option(string);
/**
* Get a option's cost with the given id from given static entry. Returns
* `Nothing` if not found.
*/
let getSelectOptionCost:
(Activatable.t, Id.Activatable.SelectOption.t) => option(int);
/**
* Get all first option IDs from the given entry.
*/
let getActiveOptions1: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
/**
* `mapActiveOptions1 f entry` takes all first option ids from the given entry
* and returns all values where the passed function, applied to each id,
* returned a `Some`, which is then unwrapped.
*/
let mapActiveOptions1:
(Id.Activatable.Option.t => option('a), Activatable_Dynamic.t) => list('a);
/**
* Get all first select option IDs from the given entry.
*/
let getActiveSelectOptions1:
Activatable_Dynamic.t => list(Id.Activatable.SelectOption.t);
/**
* Get all second option IDs from the given entry.
*/
let getActiveOptions2: Activatable_Dynamic.t => list(Id.Activatable.Option.t);
/**
* Get all second option ids from the given entry, sorted by their first option
* id in a map.
*/
let getActiveOptions2Map:
Activatable_Dynamic.t => SelectOption.Map.t(list(Id.Activatable.Option.t));
let getOption: (int, singleWithId) => option(Id.Activatable.Option.t);
let getOption1: singleWithId => option(Id.Activatable.Option.t);
let getOption2: singleWithId => option(Id.Activatable.Option.t);
let getOption3: singleWithId => option(Id.Activatable.Option.t);
let getCustomInput: Id.Activatable.Option.t => option(string);
let getGenericId: Id.Activatable.Option.t => option(int);
let lookupMap: (Ley_IntMap.key, Ley_IntMap.t('b), 'b => 'a) => option('a);
let getSkillFromOption:
(Static.t, Id.Activatable.Option.t) => option(Skill.Static.t);

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