diff --git a/.gitignore b/.gitignore index e63e3301..f4078b46 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ dist # ESLint .eslintcache + +# Junit results +results.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 86353bc6..c3370fa4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ image: node:latest stages: - - mytest + - prepare - test - build - deploy @@ -12,29 +12,37 @@ cache: &global_cache - package-lock.json policy: pull paths: - - .npm/ + - node_modules/ -lint: - stage: test - before_script: - - npm ci --cache .npm --prefer-offline +install-dependencies: + stage: prepare script: - - npm run lint + - npm install cache: <<: *global_cache policy: pull-push -# Test: -# stage: Test -# script: -# - npm test -# cache: -# <<: *global_cache +lint: + stage: test + script: + - npm run lint + cache: + <<: *global_cache + +test: + stage: test + script: + - npm run test:ci + cache: + <<: *global_cache + artifacts: + when: always + reports: + junit: + - results.xml build: stage: build - before_script: - - npm ci --cache .npm --prefer-offline script: - npm run build cache: diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..fab51eba --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "gruntfuggly.todo-tree", + "eg2.vscode-npm-script" + ] +} diff --git a/package-lock.json b/package-lock.json index 58ddfec0..e22e1242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -110,6 +110,12 @@ "fastq": "^1.6.0" } }, + "@types/jasmine": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.6.2.tgz", + "integrity": "sha512-AzfesNFLvOs6Q1mHzIsVJXSeUnqVh4ZHG8ngygKJfbkcSLwzrBVm/LKa+mR8KrOfnWtUL47112gde1MC0IXqpQ==", + "dev": true + }, "@types/jquery": { "version": "3.5.5", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz", @@ -610,6 +616,12 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1571,6 +1583,12 @@ } } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", @@ -1733,6 +1751,12 @@ "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2678,7 +2702,7 @@ } }, "foundry-pc-types": { - "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#4ae5653e74e79bb6b4bd23b094dee066a0c7723a", + "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#4e20e5c9cb1b3cd2e44555d7acfa89a3cf63f6ce", "from": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "dev": true, "requires": { @@ -3748,6 +3772,41 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "jasmine": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.3.tgz", + "integrity": "sha512-Th91zHsbsALWjDUIiU5d/W5zaYQsZFMPTdeNmi8GivZPmAaUAK8MblSG3yQI4VMGC/abF2us7ex60NH1AAIMTA==", + "dev": true, + "requires": { + "glob": "^7.1.6", + "jasmine-core": "~3.6.0" + } + }, + "jasmine-core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.6.0.tgz", + "integrity": "sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==", + "dev": true + }, + "jasmine-reporters": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", + "integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "xmldom": "^0.1.22" + } + }, + "jasmine-xml-reporter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jasmine-xml-reporter/-/jasmine-xml-reporter-1.2.1.tgz", + "integrity": "sha1-fKoqUYAv7+2+JLPqinrUJ8nqfbM=", + "dev": true, + "requires": { + "jasmine-reporters": "^2.2.0" + } + }, "js-base64": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", @@ -4326,6 +4385,12 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -5993,6 +6058,24 @@ "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", @@ -6514,6 +6597,20 @@ "glob": "^7.1.2" } }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -6949,6 +7046,12 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -7102,6 +7205,12 @@ "object.assign": "^4.1.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "zip-stream": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.0.4.tgz", diff --git a/package.json b/package.json index 7099f55d..eb25234e 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,15 @@ "clean": "gulp clean && gulp link --clean", "update": "npm install --save-dev git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "lint": "eslint 'src/**/*.ts' --cache", - "lint:fix": "eslint 'src/**/*.ts' --cache --fix" + "lint:fix": "eslint 'src/**/*.ts' --cache --fix", + "test": "ts-node ./node_modules/jasmine/bin/jasmine", + "test:ci": "ts-node ./node_modules/jasmine-xml-reporter/bin/jasmine --junitreport", + "format": "prettier --write 'src/**/*.(ts|json|scss)'" }, "author": "", "license": "", "devDependencies": { + "@types/jasmine": "^3.6.2", "@typescript-eslint/eslint-plugin": "^4.11.0", "@typescript-eslint/parser": "^4.11.0", "archiver": "^5.1.0", @@ -31,10 +35,13 @@ "gulp-sass": "^4.1.0", "gulp-typescript": "^6.0.0-alpha.1", "husky": "^4.3.6", + "jasmine": "^3.6.3", + "jasmine-xml-reporter": "^1.2.1", "json-stringify-pretty-compact": "^2.0.0", "lint-staged": "^10.5.3", "prettier": "^2.2.1", "sass": "^1.30.0", + "ts-node": "^9.1.1", "typescript": "^4.1.3", "yargs": "^16.2.0" }, @@ -44,6 +51,7 @@ } }, "lint-staged": { - "*.ts": "eslint --cache --fix" + "*.ts": "eslint --cache --fix", + "*.(json|scss)": "prettier --write" } } diff --git a/spec/support/ds4rolls.spec.ts b/spec/support/ds4rolls.spec.ts new file mode 100644 index 00000000..f56fa46c --- /dev/null +++ b/spec/support/ds4rolls.spec.ts @@ -0,0 +1,272 @@ +import { + rollCheckMultipleDice, + rollCheckSingleDie, + RollOptions, + RollResult, + RollResultStatus, +} from "../../src/module/rolls/roll-executor"; +import { RollProvider } from "../../src/module/rolls/roll-provider"; + +import "jasmine"; + +describe("DS4 Rolls with one die and no modifications.", () => { + it("Should do a regular success roll.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(4); + + expect(rollCheckSingleDie(12, new RollOptions(), rollProvider)).toEqual( + new RollResult(4, RollResultStatus.SUCCESS, [4]), + ); + }); + + it("Should do a single success roll on success upper edge case.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(4); + + expect(rollCheckSingleDie(4, new RollOptions(), rollProvider)).toEqual( + new RollResult(4, RollResultStatus.SUCCESS, [4]), + ); + }); + + it("Should do a single failure roll on lower edge case.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(5); + + expect(rollCheckSingleDie(4, new RollOptions(), rollProvider)).toEqual( + new RollResult(0, RollResultStatus.FAILURE, [5]), + ); + }); + + it("Should do a single failure roll on upper edge case '19'.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(19); + + expect(rollCheckSingleDie(4, new RollOptions(), rollProvider)).toEqual( + new RollResult(0, RollResultStatus.FAILURE, [19]), + ); + }); + + it("Should do a single crit success roll on '1'.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(1); + + expect(rollCheckSingleDie(4, new RollOptions(), rollProvider)).toEqual( + new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), + ); + }); + + it("Should do a single crit failure roll on '20'.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(20); + + expect(rollCheckSingleDie(4, new RollOptions(), rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), + ); + }); +}); + +describe("DS4 Rolls with one die and crit roll modifications.", () => { + it("Should do a crit success on `1`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(1); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), + ); + }); + + it("Should do a crit success on `maxCritSucc`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(2); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [2]), + ); + }); + + it("Should do a success on lower edge case `3`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(3); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(3, RollResultStatus.SUCCESS, [3]), + ); + }); + + it("Should do a success on upper edge case `18`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(18); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(0, RollResultStatus.FAILURE, [18]), + ); + }); + + it("Should do a crit fail on `minCritFail`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(19); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19]), + ); + }); + + it("Should do a crit fail on `20`", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); + + rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(20); + + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), + ); + }); +}); + +describe("DS4 Rools with multiple dice and no modifiers.", () => { + it("Should do a crit fail on `20` for first roll.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([20, 15, 6]); + + expect(rollCheckMultipleDice(48, new RollOptions(), rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20, 15, 6]), + ); + }); + + it("Should succeed with all rolls crit successes.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([1, 1, 1]); + + expect(rollCheckMultipleDice(48, new RollOptions(), rollProvider)).toEqual( + new RollResult(48, RollResultStatus.SUCCESS, [1, 1, 1]), + ); + }); + + it("Should succeed with the last roll not being suficient.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 15, 15]); + + expect(rollCheckMultipleDice(48, new RollOptions(), rollProvider)).toEqual( + new RollResult(30, RollResultStatus.SUCCESS, [15, 15, 15]), + ); + }); + + it("Should succeed with the last roll a crit success.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 15, 1]); + + expect(rollCheckMultipleDice(48, new RollOptions(), rollProvider)).toEqual( + new RollResult(35, RollResultStatus.SUCCESS, [1, 15, 15]), + ); + }); + + it("Should succeed with the last roll being 20 and one crit success.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 1, 20]); + + expect(rollCheckMultipleDice(48, new RollOptions(), rollProvider)).toEqual( + new RollResult(40, RollResultStatus.SUCCESS, [1, 20, 15]), + ); + }); +}); + +describe("DS4 Rools with multiple dice and min/max modifiers.", () => { + it("Should do a crit fail on `19` for first roll.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([19, 15, 6]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19, 15, 6]), + ); + }); + + it("Should succeed with all rolls crit successes (1 and 2).", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([2, 1, 2]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(48, RollResultStatus.SUCCESS, [2, 2, 1]), + ); + }); + + it("Should succeed with the last roll not being suficient.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 15, 15]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(30, RollResultStatus.SUCCESS, [15, 15, 15]), + ); + }); + + it("Should succeed with the last roll a crit success `2`.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 15, 2]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(35, RollResultStatus.SUCCESS, [2, 15, 15]), + ); + }); + + it("Should succeed with the last roll being `20` and one crit success '2'.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 2, 20]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(40, RollResultStatus.SUCCESS, [2, 20, 15]), + ); + }); + + it("Should succeed with the last roll being `19` and one crit success '2'.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([15, 2, 19]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(39, RollResultStatus.SUCCESS, [2, 19, 15]), + ); + }); +}); + +describe("DS4 Rools with multiple dice and fail modifiers.", () => { + it("Should do a crit fail on `19` for first roll.", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([19, 15, 6]); + + expect(rollCheckMultipleDice(48, { minCritFail: 19 } as RollOptions, rollProvider)).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19, 15, 6]), + ); + }); +}); + +describe("DS4 Rools with multiple dice and success modifiers.", () => { + it("Should succeed with all rolls crit successes (1 and 2).", () => { + const rollProvider: RollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); + + rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue([2, 1, 2]); + + expect(rollCheckMultipleDice(48, { maxCritSucc: 2 } as RollOptions, rollProvider)).toEqual( + new RollResult(48, RollResultStatus.SUCCESS, [2, 2, 1]), + ); + }); +}); diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json new file mode 100644 index 00000000..6d97768a --- /dev/null +++ b/spec/support/jasmine.json @@ -0,0 +1,7 @@ +{ + "spec_dir": "spec", + "spec_files": ["**/*[sS]pec.ts"], + "helpers": ["helpers/**/*.ts"], + "stopSpecOnExpectationFailure": false, + "random": true +} diff --git a/src/assets/DS4-DEF.png b/src/assets/DS4-DEF.png new file mode 100644 index 00000000..40d8cdc0 Binary files /dev/null and b/src/assets/DS4-DEF.png differ diff --git a/src/assets/DS4-HP.png b/src/assets/DS4-HP.png new file mode 100644 index 00000000..afa45bf0 Binary files /dev/null and b/src/assets/DS4-HP.png differ diff --git a/src/assets/DS4-INI.png b/src/assets/DS4-INI.png new file mode 100644 index 00000000..5c90b2f1 Binary files /dev/null and b/src/assets/DS4-INI.png differ diff --git a/src/assets/DS4-MAT.png b/src/assets/DS4-MAT.png index 09e41dfc..52238d1b 100644 Binary files a/src/assets/DS4-MAT.png and b/src/assets/DS4-MAT.png differ diff --git a/src/assets/DS4-MR.png b/src/assets/DS4-MR.png new file mode 100644 index 00000000..da203ff2 Binary files /dev/null and b/src/assets/DS4-MR.png differ diff --git a/src/assets/DS4-MRA.png b/src/assets/DS4-MRA.png index 82e6501b..ae97840d 100644 Binary files a/src/assets/DS4-MRA.png and b/src/assets/DS4-MRA.png differ diff --git a/src/assets/DS4-MSC.png b/src/assets/DS4-MSC.png new file mode 100644 index 00000000..e13b19f5 Binary files /dev/null and b/src/assets/DS4-MSC.png differ diff --git a/src/assets/DS4-RAT.png b/src/assets/DS4-RAT.png index c292458d..3f34ca61 100644 Binary files a/src/assets/DS4-RAT.png and b/src/assets/DS4-RAT.png differ diff --git a/src/assets/DS4-SPC.png b/src/assets/DS4-SPC.png new file mode 100644 index 00000000..f46e35e9 Binary files /dev/null and b/src/assets/DS4-SPC.png differ diff --git a/src/assets/DS4-TSC.png b/src/assets/DS4-TSC.png new file mode 100644 index 00000000..e6244bfc Binary files /dev/null and b/src/assets/DS4-TSC.png differ diff --git a/src/ds4.scss b/src/ds4.scss index f91ff4ba..b2e83661 100644 --- a/src/ds4.scss +++ b/src/ds4.scss @@ -17,4 +17,5 @@ @import "scss/components/tabs"; @import "scss/components/items"; @import "scss/components/description"; + @import "scss/components/character_values"; } diff --git a/src/lang/en.json b/src/lang/en.json index e0e916f5..28764b4d 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -56,5 +56,30 @@ "DS4.ArmorMaterialTypeChain": "Chain", "DS4.ArmorMaterialTypeChainAbbr": "Chain", "DS4.ArmorMaterialTypePlate": "Plate", - "DS4.ArmorMaterialTypePlateAbbr": "Plate" + "DS4.ArmorMaterialTypePlateAbbr": "Plate", + "DS4.AttributeBody": "Body", + "DS4.AttributeMobility": "Mobility", + "DS4.AttributeMind": "Mind", + "DS4.TraitStrength": "Strength", + "DS4.TraitConstitution": "Constitution", + "DS4.TraitAgility": "Agility", + "DS4.TraitDexterity": "Dexterity", + "DS4.TraitIntellect": "Intellect", + "DS4.TraitAura": "Aura", + "DS4.CombatValuesHitPoints": "Hit Points", + "DS4.CombatValuesDefense": "Defense", + "DS4.CombatValuesInitiative": "Initiative", + "DS4.CombatValuesMovement": "Movement", + "DS4.CombatValuesMeleeAttack": "Melee Attack", + "DS4.CombatValuesRangedAttack": "Ranged Attack", + "DS4.CombatValuesSpellcasting": "Spellcasting", + "DS4.CombatValuesTargetedSpellcasting": "Targeted Spellcasting", + "DS4.BaseInfoRace": "Race", + "DS4.BaseInfoClass": "Class", + "DS4.BaseInfoHeroClass": "Hero Class", + "DS4.BaseInfoRacialAbilities": "Racial Abilites", + "DS4.ProgressionLevel": "Level", + "DS4.ProgressionExperiencePoints": "Experience Points", + "DS4.ProgressionTalentPoints": "Talent Points", + "DS4.ProgressionProgressPoints": "Progress Points" } diff --git a/src/module/actor/actor-data.ts b/src/module/actor/actor-data.ts index a202f15f..93385b65 100644 --- a/src/module/actor/actor-data.ts +++ b/src/module/actor/actor-data.ts @@ -1,26 +1,65 @@ export interface DS4ActorDataType { attributes: DS4ActorDataAttributes; traits: DS4ActorDataTraits; + combatValues: DS4ActorDataCombatValues; + baseInfo: DS4ActorDataBaseInfo; + progression: DS4ActorDataProgression; } interface DS4ActorDataAttributes { body: BodyAttribute; - mobility: ExtensibleData; - mind: ExtensibleData; + mobility: ModifiableData; + mind: ModifiableData; } -interface ExtensibleData { - initial: T; +export interface ModifiableData { + base: T; + mod: T; + total?: T; +} + +interface UsableResource { + total: T; + used: T; +} + +interface CurrentData extends ModifiableData { + current: T; } // Blueprint in case we need more detailed differentiation -type BodyAttribute = ExtensibleData; +type BodyAttribute = ModifiableData; interface DS4ActorDataTraits { - strength: ExtensibleData; - constitution: ExtensibleData; - agility: ExtensibleData; - dexterity: ExtensibleData; - intellect: ExtensibleData; - aura: ExtensibleData; + strength: ModifiableData; + constitution: ModifiableData; + agility: ModifiableData; + dexterity: ModifiableData; + intellect: ModifiableData; + aura: ModifiableData; +} + +interface DS4ActorDataCombatValues { + hitPoints: CurrentData; + defense: ModifiableData; + initiative: ModifiableData; + movement: ModifiableData; + meleeAttack: ModifiableData; + rangedAttack: ModifiableData; + spellcasting: ModifiableData; + targetedSpellcasting: ModifiableData; +} + +interface DS4ActorDataBaseInfo { + race: string; + class: string; + heroClass: string; + racialAbilities: string; +} + +interface DS4ActorDataProgression { + level: number; + experiencePoints: number; + talentPoints: UsableResource; + progressPoints: UsableResource; } diff --git a/src/module/actor/actor-sheet.ts b/src/module/actor/actor-sheet.ts index 5ca03bfe..d08e488f 100644 --- a/src/module/actor/actor-sheet.ts +++ b/src/module/actor/actor-sheet.ts @@ -30,7 +30,7 @@ export class DS4ActorSheet extends ActorSheet { /** @override */ prepareDerivedData(): void { const data = this.data; - this._prepareCombatValues(data); - } - - private _prepareCombatValues(data: ActorData): void { - const hitPointsModifier = getProperty(data, "data.combatValues.hitPoints.modifier") || 0; - const actorData = data.data; - setProperty( - data, - "data.combatValues.hitPoints.max", - actorData.attributes.body.initial + actorData.traits.constitution.initial + 10 + hitPointsModifier, + const attributes = data.data.attributes; + Object.values(attributes).forEach( + (attribute: ModifiableData) => (attribute.total = attribute.base + attribute.mod), ); - const defenseModifier = getProperty(data, "data.combatValues.defense.modifier") || 0; - setProperty( - data, - "data.combatValues.defense.value", - actorData.attributes.body.initial + - actorData.traits.constitution.initial + - this._getArmorValue() + - defenseModifier, - ); - } + const traits = data.data.traits; + Object.values(traits).forEach((trait: ModifiableData) => (trait.total = trait.base + trait.mod)); - private _getArmorValue(): number { - return this.data["items"] - .filter((item) => ["armor", "shield"].includes(item.type)) - .filter((item) => item.data.equipped) - .map((item) => item.data.armorValue) - .reduce((a, b) => a + b, 0); + const combatValues = data.data.combatValues; + Object.values(combatValues).forEach( + (combatValue: ModifiableData) => (combatValue.total = combatValue.base + combatValue.mod), + ); } } diff --git a/src/module/config.ts b/src/module/config.ts index 3ea52ba3..8b451e74 100644 --- a/src/module/config.ts +++ b/src/module/config.ts @@ -10,7 +10,6 @@ export const DS4 = { /** * Define the set of acttack types that can be performed with weapon items - * @type {Object} */ attackTypes: { melee: "DS4.AttackTypeMelee", @@ -19,8 +18,7 @@ export const DS4 = { }, /** - * * Define the file paths to icon images - * @type {Object} + * Define the file paths to icon images */ attackTypesIcons: { melee: "systems/ds4/assets/DS4-MAT.png", @@ -30,7 +28,6 @@ export const DS4 = { /** * Define the set of item availabilties - * @type {Object} */ itemAvailabilities: { unset: "DS4.ItemAvailabilityUnset", @@ -43,8 +40,7 @@ export const DS4 = { }, /** - * * Define the set of item types - * @type {Object} + * Define the set of item types */ itemTypes: { weapon: "DS4.ItemTypeWeapon", @@ -55,8 +51,7 @@ export const DS4 = { }, /** - * * Define the set of armor types, a character may only wear one item of each at any given time - * @type {Object} + * Define the set of armor types, a character may only wear one item of each at any given time */ armorTypes: { body: "DS4.ArmorTypeBody", @@ -67,8 +62,7 @@ export const DS4 = { }, /** - * * Define abbreviations for the armor types - * @type {Object} + * Define abbreviations for the armor types */ armorTypesAbbr: { body: "DS4.ArmorTypeBodyAbbr", @@ -79,8 +73,7 @@ export const DS4 = { }, /** - * * Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties - * @type {Object} + * Define the set of armor materials, used to determine if a characer may wear the armor without additional penalties */ armorMaterialTypes: { cloth: "DS4.ArmorMaterialTypeCloth", @@ -90,8 +83,7 @@ export const DS4 = { }, /** - * * Define the abbreviations of armor materials - * @type {Object} + * Define the abbreviations of armor materials */ armorMaterialTypesAbbr: { cloth: "DS4.ArmorMaterialTypeClothAbbr", @@ -99,4 +91,59 @@ export const DS4 = { chain: "DS4.ArmorMaterialTypeChainAbbr", plate: "DS4.ArmorMaterialTypePlateAbbr", }, + + /** + * Define the set of attributes a character has + */ + attributes: { + body: "DS4.AttributeBody", + mobility: "DS4.AttributeMobility", + mind: "DS4.AttributeMind", + }, + + /** + * Define the set of traits a character has + */ + traits: { + strength: "DS4.TraitStrength", + constitution: "DS4.TraitConstitution", + agility: "DS4.TraitAgility", + dexterity: "DS4.TraitDexterity", + intellect: "DS4.TraitIntellect", + aura: "DS4.TraitAura", + }, + + /** + * Define the set of combat values a character has + */ + combatValues: { + hitPoints: "DS4.CombatValuesHitPoints", + defense: "DS4.CombatValuesDefense", + initiative: "DS4.CombatValuesInitiative", + movement: "DS4.CombatValuesMovement", + meleeAttack: "DS4.CombatValuesMeleeAttack", + rangedAttack: "DS4.CombatValuesRangedAttack", + spellcasting: "DS4.CombatValuesSpellcasting", + targetedSpellcasting: "DS4.CombatValuesTargetedSpellcasting", + }, + + /** + * Define the base info of a character + */ + baseInfo: { + race: "DS4.BaseInfoRace", + class: "DS4.BaseInfoClass", + heroClass: "DS4.BaseInfoHeroClass", + racialAbilities: "DS4.BaseInfoRacialAbilities", + }, + + /** + * Definme the progression info of a character + */ + progression: { + level: "DS4.ProgressionLevel", + experiencePoints: "DS4.ProgressionExperiencePoints", + talentPoints: "DS4.ProgressionTalentPoints", + progressPoints: "DS4.ProgressionProgressPoints", + }, }; diff --git a/src/module/ds4.ts b/src/module/ds4.ts index 5cf5d4a0..ba0d3017 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -58,6 +58,12 @@ Hooks.once("setup", function () { "armorTypesAbbr", "armorMaterialTypes", "armorMaterialTypesAbbr", + "armorMaterialTypes", + "attributes", + "traits", + "combatValues", + "baseInfo", + "progression", ]; // Exclude some from sorting where the default order matters diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts new file mode 100644 index 00000000..204f5edc --- /dev/null +++ b/src/module/rolls/roll-executor.ts @@ -0,0 +1,99 @@ +import { DS4RollProvider, RollProvider } from "./roll-provider"; + +export function ds4test(testValue: number, rollOptions: RollOptions = new RollOptions()): RollResult { + const finalRollValue = testValue; + if (finalRollValue <= 20) { + return rollCheckSingleDie(finalRollValue, rollOptions); + } else { + return rollCheckMultipleDice(finalRollValue, rollOptions); + } +} + +export function rollCheckSingleDie( + testValue: number, + rollOptions: RollOptions, + provider: RollProvider = new DS4RollProvider(), +): RollResult { + const roll = provider.getNextRoll(); + const dice = [roll]; + + if (roll <= rollOptions.maxCritSucc) { + return new RollResult(testValue, RollResultStatus.CRITICAL_SUCCESS, dice); + } else if (roll >= rollOptions.minCritFail) { + return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); + } else { + if (roll <= testValue) { + return new RollResult(roll, RollResultStatus.SUCCESS, dice); + } else { + return new RollResult(0, RollResultStatus.FAILURE, dice); + } + } +} + +export function rollCheckMultipleDice( + testValue: number, + rollOptions: RollOptions, + provider: RollProvider = new DS4RollProvider(), +): RollResult { + const finalCheck = testValue % 20; + const numberOfDice = Math.ceil(testValue / 20); + + const dice = provider.getNextRolls(numberOfDice); + + const firstResult = dice[0]; + + if (firstResult >= rollOptions.minCritFail) { + return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); + } + + const partitionCallback = (prev: [Array, Array], cur: number) => { + if (cur <= rollOptions.maxCritSucc) { + prev[0].push(cur); + } else { + prev[1].push(cur); + } + return prev; + }; + + const [critSuccesses, otherRolls] = dice + .reduce(partitionCallback, [[], []]) + .map((a) => a.sort((r1, r2) => r2 - r1)); + + const sortedRollResults: Array = critSuccesses.concat(otherRolls); + + const evaluationResult = sortedRollResults + .map((value, index) => { + if (index == numberOfDice - 1) { + if (value == 1) { + return finalCheck; + } else { + return value <= finalCheck ? value : 0; + } + } else { + if (value <= rollOptions.maxCritSucc) { + return 20; + } else { + return value; + } + } + }) + .reduce((a, b) => a + b); + + return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults); +} + +export class RollOptions { + public maxCritSucc = 1; + public minCritFail = 20; +} + +export class RollResult { + constructor(public value: number, public status: RollResultStatus, public dice: Array) {} +} + +export enum RollResultStatus { + FAILURE, + SUCCESS, + CRITICAL_FAILURE, + CRITICAL_SUCCESS, +} diff --git a/src/module/rolls/roll-provider.ts b/src/module/rolls/roll-provider.ts new file mode 100644 index 00000000..2e4982e0 --- /dev/null +++ b/src/module/rolls/roll-provider.ts @@ -0,0 +1,16 @@ +export class DS4RollProvider { + getNextRoll(): number { + return new Roll("1d20").roll().total; + } + + getNextRolls(amount: number): Array { + return Array(amount) + .fill(0) + .map(() => this.getNextRoll()); + } +} + +export interface RollProvider { + getNextRoll(): number; + getNextRolls(number: number): Array; +} diff --git a/src/scss/components/_character_values.scss b/src/scss/components/_character_values.scss new file mode 100644 index 00000000..8c5e394d --- /dev/null +++ b/src/scss/components/_character_values.scss @@ -0,0 +1,99 @@ +header.sheet-header { + .character-values { + flex: 0 0 100%; + .attributes-traits { + margin-top: $margin-sm; + .attribute { + .attribute-label { + font-family: $font-heading; + font-size: 2em; + text-align: center; + } + .attribute-value { + border: 2px groove $c-border-groove; + line-height: 26px; + font-size: 1.5em; + text-align: center; + padding-left: 2px; + padding-right: 2px; + gap: 0; + input, + .attribute-value-total { + grid-column: span 2; + } + } + } + .trait { + .trait-label { + color: transparent; + font-family: $font-heading; + font-size: 2em; + text-align: center; + //text-shadow: -1px 1px 0 $c-black, 1px 1px 0 $c-black, 1px -1px 0 $c-black, -1px -1px 0 $c-black; + -webkit-text-stroke: 1px $c-black; + } + .trait-value { + border: 2px groove $c-border-groove; + font-size: 1.5em; + line-height: 26px; + text-align: center; + padding-left: 2px; + padding-right: 2px; + gap: 0; + input, + .trait-value-total { + grid-column: span 2; + } + } + } + } + .combat-values { + margin-top: $margin-sm; + .combat-value-with-formula { + display: grid; + place-items: center; + $size: 60px; + row-gap: $margin-sm; + .combat-value { + @include centered-content; + height: $size; + width: $size; + flex: 0 0 auto; + background-size: contain; + font-size: 1.5em; + &.hitPoints { + background-image: url("assets/DS4-HP.png"); + } + &.defense { + background-image: url("assets/DS4-DEF.png"); + } + &.initiative { + background-image: url("assets/DS4-INI.png"); + } + &.movement { + background-image: url("assets/DS4-MR.png"); + } + &.meleeAttack { + background-image: url("assets/DS4-MAT.png"); + } + &.rangedAttack { + background-image: url("assets/DS4-RAT.png"); + } + &.spellcasting { + background-image: url("assets/DS4-SPC.png"); + } + &.targetedSpellcasting { + background-image: url("assets/DS4-TSC.png"); + } + } + + .combat-value-formula { + width: $size; + input { + text-align: center; + } + } + } + } + } +} diff --git a/src/scss/components/_forms.scss b/src/scss/components/_forms.scss index acd5f812..2a992d7b 100644 --- a/src/scss/components/_forms.scss +++ b/src/scss/components/_forms.scss @@ -5,7 +5,7 @@ $header-top-margin: 5px; header.sheet-header { - flex: 0 0 210px; + flex: 0 0 auto; overflow: hidden; display: flex; flex-direction: row; diff --git a/src/scss/components/_items.scss b/src/scss/components/_items.scss index d35a84a5..3f089ca3 100644 --- a/src/scss/components/_items.scss +++ b/src/scss/components/_items.scss @@ -51,7 +51,6 @@ margin-top: 0px; padding-top: 0px; } - } } @@ -61,4 +60,4 @@ padding-left: 1em; border-bottom: 2px groove $c-border-groove; font-weight: bold; -} \ No newline at end of file +} diff --git a/src/scss/global/_flex.scss b/src/scss/global/_flex.scss index 4027bbc7..ca8ae406 100644 --- a/src/scss/global/_flex.scss +++ b/src/scss/global/_flex.scss @@ -70,3 +70,7 @@ .flex-between { justify-content: space-between; } + +.flex-around { + justify-content: space-around; +} diff --git a/src/scss/utils/_mixins.scss b/src/scss/utils/_mixins.scss index 46d30323..7e028c29 100644 --- a/src/scss/utils/_mixins.scss +++ b/src/scss/utils/_mixins.scss @@ -14,3 +14,8 @@ @mixin hide { display: none; } + +@mixin centered-content { + display: grid; + place-items: center; +} diff --git a/src/scss/utils/_variables.scss b/src/scss/utils/_variables.scss index deee5de0..6b5cbd78 100644 --- a/src/scss/utils/_variables.scss +++ b/src/scss/utils/_variables.scss @@ -1,3 +1,6 @@ $padding-sm: 5px; $padding-md: 10px; $padding-lg: 20px; +$margin-sm: $padding-sm; +$margin-md: $padding-md; +$margin-lg: $padding-lg; diff --git a/src/template.json b/src/template.json index f25fe366..b78fd3e1 100644 --- a/src/template.json +++ b/src/template.json @@ -6,33 +6,95 @@ "templates": [], "attributes": { "body": { - "initial": 8 + "base": 0, + "mod": 0 }, "mobility": { - "initial": 0 + "base": 0, + "mod": 0 }, "mind": { - "initial": 0 + "base": 0, + "mod": 0 } }, "traits": { "strength": { - "initial": 4 + "base": 0, + "mod": 0 }, "constitution": { - "initial": 0 + "base": 0, + "mod": 0 }, "agility": { - "initial": 0 + "base": 0, + "mod": 0 }, "dexterity": { - "initial": 0 + "base": 0, + "mod": 0 }, "intellect": { - "initial": 0 + "base": 0, + "mod": 0 }, "aura": { - "initial": 0 + "base": 0, + "mod": 0 + } + }, + "combatValues": { + "hitPoints": { + "base": 0, + "mod": 0, + "current": 0 + }, + "defense": { + "base": 0, + "mod": 0 + }, + "initiative": { + "base": 0, + "mod": 0 + }, + "movement": { + "base": 0, + "mod": 0 + }, + "meleeAttack": { + "base": 0, + "mod": 0 + }, + "rangedAttack": { + "base": 0, + "mod": 0 + }, + "spellcasting": { + "base": 0, + "mod": 0 + }, + "targetedSpellcasting": { + "base": 0, + "mod": 0 + } + }, + "baseInfo": { + "race": "", + "class": "", + "heroClass": "", + "racialAbilities": "" + }, + "progression": { + "level": 0, + "experiencePoints": 0, + "talentPoints": { + "total": 0, + "used": 0 + }, + "progressPoints": { + "total": 0, + "used": 0 } } } diff --git a/src/templates/actor/actor-sheet.hbs b/src/templates/actor/actor-sheet.hbs index 4aa0540d..99de9e32 100644 --- a/src/templates/actor/actor-sheet.hbs +++ b/src/templates/actor/actor-sheet.hbs @@ -4,45 +4,230 @@

+
+
{{!-- The grid classes are defined in scss/global/_grid.scss. To use, use both the "grid" and "grid-Ncol" class where "N" can be any number from 1 to 12 and will create that number of columns. --}} -
+
{{!-- "flex-group-center" is also defined in the _grid.scss file and it will add a small amount of padding, a border, and will center all of its child elements content and text. --}} -
- -
- - / - +
+ +
+
-
- -
- - / - +
+
+
+ +
+ +
+
+
+ +
+ / + +
+
+
+ +
+ / + +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
- {{!-- The grid classes are defined in scss/global/_grid.scss. To use, use both the "grid" and "grid-Ncol" - class where "N" can be any number from 1 to 12 and will create that number of columns. --}} -
- {{#each data.abilities as |ability key|}} -
- - - {{numberFormat ability.mod decimals=0 sign=true}} +
+
+
+ + = + {{data.attributes.body.total}}
+
+
+
+ + = + {{data.attributes.mobility.total}}
+
+
+
+ + = + {{data.attributes.mind.total}}
+
+
+
+ + = + {{data.traits.strength.total}}
+
+
+
+ + = + {{data.traits.agility.total}}
+
+
+
+ + = + {{data.traits.intellect.total}}
+
+
+
+ + = + {{data.traits.constitution.total}}
+
+
+
+ + = + {{data.traits.dexterity.total}}
+
+
+
+ + = + {{data.traits.aura.total}}
+
+
+
+
+
{{data.combatValues.hitPoints.total}}
+
+
+
+
{{data.combatValues.defense.total}}
+
+
+
+
{{data.combatValues.initiative.total}}
+
+
+
+
{{data.combatValues.movement.total}}
+
+
+
+
{{data.combatValues.meleeAttack.total}}
+
+
+
+
{{data.combatValues.rangedAttack.total}}
+
+
+
+
{{data.combatValues.spellcasting.total}}
+
+
+
+
{{data.combatValues.targetedSpellcasting.total}} +
+
- {{/each}}
@@ -63,4 +248,4 @@ {{!-- Items Tab --}} {{> systems/ds4/templates/actor/partials/items.hbs}} - + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 9293923b..f988dd5b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ -{ - "compilerOptions": { - "target": "ES2017", - "lib": ["DOM", "ES6", "ES2017"], - "types": ["foundry-pc-types"] - } -} +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["DOM", "ES6", "ES2017"], + "types": ["foundry-pc-types"], + "esModuleInterop": true + } +}