Initial import
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
## Prism RPG RPG for Foundry Virtual TableTop
|
||||||
|
|
||||||
|
The Official game system for playing Prism RPG TTRPG: The Role Playing Game on FoundryVTT. This fully functional system is the foundational framework to build your game.
|
||||||
|
|
||||||
|
This product's format, programming code, and presentation is copyrighted by Prism RPG Games LLC.
|
||||||
|
|
||||||
|
This system & product are used with permission granted as part of the partnership agreement between Foundry Gaming LLC and Prism RPG Games LLC. It uses the following trademarks and/or copyrights:
|
||||||
|
|
||||||
|
© 2025 Prism RPG Games. Content copyright Ted McClintock, Prism RPG Games LLC. All Rights Reserved. Prism RPG® is a Registered Trademark of Prism RPG Games LLC. All Rights Reserved.
|
||||||
|
|
||||||
|
Prism RPG Games is ©2025 Prism RPG Games, LLC. All rights reserved. Prism RPG, Prism RPG Games, and their associated logos are trademarks of Prism RPG Games, LLC. https://lethalfantasy.com/
|
||||||
|
|
||||||
|
For inquiries on developing content for this ruleset please contact Lethalted@lethalfantasy.com
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
Please join our Discord server Prism RPG games https://discord.gg/UDvnnyvreV
|
||||||
|
|
||||||
|
It's the place to ask questions on how to use the system, make feature request and follow the development of the system.
|
||||||
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 818 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg style="height: 512px; width: 512px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><g class="" style="" transform="translate(0,0)"><path d="M373.47 25.5c-33.475-.064-67.614 13.444-94.44 43.156l37.22 145.156-33.437.032 35.343 132.093-116.718-188.375 50.03 5.375L202.5 47.312C120.437-1.43 4.756 40.396 8.5 158.156c4.402 138.44 191.196 184.6 247.406 331.625 59.376-147.035 251.26-184.33 246.656-331.624-2.564-82.042-64.6-132.532-129.093-132.656z" fill="#fff" fill-opacity="1"></path></g></svg>
|
||||||
|
After Width: | Height: | Size: 506 B |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 409 KiB |
|
After Width: | Height: | Size: 623 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 353 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
## v13.0.12
|
||||||
|
|
||||||
|
- Fix favor/disfavor
|
||||||
|
- Fix granted dice
|
||||||
|
- Cosmetic fixes
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import jsdoc from 'eslint-plugin-jsdoc';
|
||||||
|
import prettier from 'eslint-plugin-prettier';
|
||||||
|
import configPrettier from 'eslint-config-prettier';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/",
|
||||||
|
"eslint.config.mjs",
|
||||||
|
"build.mjs",
|
||||||
|
"gulpfile.js"
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
browser: true,
|
||||||
|
es2022: true,
|
||||||
|
node: true,
|
||||||
|
jquery: true,
|
||||||
|
},
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
jsdoc,
|
||||||
|
prettier
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'array-bracket-spacing': ['warn', 'never'],
|
||||||
|
'array-callback-return': 'warn',
|
||||||
|
'arrow-spacing': 'warn',
|
||||||
|
'comma-dangle': ['warn', 'never'],
|
||||||
|
'comma-style': 'warn',
|
||||||
|
'computed-property-spacing': 'warn',
|
||||||
|
'constructor-super': 'error',
|
||||||
|
'default-param-last': 'warn',
|
||||||
|
'dot-location': ['warn', 'property'],
|
||||||
|
'eol-last': ['error', 'always'],
|
||||||
|
'eqeqeq': ['warn', 'smart'],
|
||||||
|
'func-call-spacing': 'warn',
|
||||||
|
'func-names': ['warn', 'never'],
|
||||||
|
'getter-return': 'warn',
|
||||||
|
'lines-between-class-members': 'warn',
|
||||||
|
'new-parens': ['warn', 'always'],
|
||||||
|
'no-alert': 'warn',
|
||||||
|
'no-array-constructor': 'warn',
|
||||||
|
'no-class-assign': 'warn',
|
||||||
|
'no-compare-neg-zero': 'warn',
|
||||||
|
'no-cond-assign': 'warn',
|
||||||
|
'no-const-assign': 'error',
|
||||||
|
'no-constant-condition': 'warn',
|
||||||
|
'no-constructor-return': 'warn',
|
||||||
|
'no-delete-var': 'warn',
|
||||||
|
'no-dupe-args': 'warn',
|
||||||
|
'no-dupe-class-members': 'warn',
|
||||||
|
'no-dupe-keys': 'warn',
|
||||||
|
'no-duplicate-case': 'warn',
|
||||||
|
'no-duplicate-imports': ['warn', { includeExports: true }],
|
||||||
|
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||||
|
'no-empty-character-class': 'warn',
|
||||||
|
'no-empty-pattern': 'warn',
|
||||||
|
'no-func-assign': 'warn',
|
||||||
|
'no-global-assign': 'warn',
|
||||||
|
'no-implicit-coercion': ['warn', { allow: ['!!'] }],
|
||||||
|
'no-implied-eval': 'warn',
|
||||||
|
'no-import-assign': 'warn',
|
||||||
|
'no-invalid-regexp': 'warn',
|
||||||
|
'no-irregular-whitespace': 'warn',
|
||||||
|
'no-iterator': 'warn',
|
||||||
|
'no-lone-blocks': 'warn',
|
||||||
|
'no-lonely-if': 'off',
|
||||||
|
'no-loop-func': 'warn',
|
||||||
|
'no-misleading-character-class': 'warn',
|
||||||
|
'no-mixed-operators': 'warn',
|
||||||
|
'no-multi-str': 'warn',
|
||||||
|
'no-multiple-empty-lines': 'warn',
|
||||||
|
'no-new-func': 'warn',
|
||||||
|
'no-new-object': 'warn',
|
||||||
|
'no-new-symbol': 'warn',
|
||||||
|
'no-new-wrappers': 'warn',
|
||||||
|
'no-nonoctal-decimal-escape': 'warn',
|
||||||
|
'no-obj-calls': 'warn',
|
||||||
|
'no-octal': 'warn',
|
||||||
|
'no-octal-escape': 'warn',
|
||||||
|
'no-promise-executor-return': 'warn',
|
||||||
|
'no-proto': 'warn',
|
||||||
|
'no-regex-spaces': 'warn',
|
||||||
|
'no-script-url': 'warn',
|
||||||
|
'no-self-assign': 'warn',
|
||||||
|
'no-self-compare': 'warn',
|
||||||
|
'no-setter-return': 'warn',
|
||||||
|
'no-sequences': 'warn',
|
||||||
|
'no-template-curly-in-string': 'warn',
|
||||||
|
'no-this-before-super': 'error',
|
||||||
|
'no-unexpected-multiline': 'warn',
|
||||||
|
'no-unmodified-loop-condition': 'warn',
|
||||||
|
'no-unneeded-ternary': 'warn',
|
||||||
|
'no-unreachable': 'warn',
|
||||||
|
'no-unreachable-loop': 'warn',
|
||||||
|
'no-unsafe-negation': ['warn', { enforceForOrderingRelations: true }],
|
||||||
|
'no-unsafe-optional-chaining': ['warn', { disallowArithmeticOperators: true }],
|
||||||
|
'no-unused-expressions': 'warn',
|
||||||
|
'no-useless-backreference': 'warn',
|
||||||
|
'no-useless-call': 'warn',
|
||||||
|
'no-useless-catch': 'warn',
|
||||||
|
'no-useless-computed-key': ['warn', { enforceForClassMembers: true }],
|
||||||
|
'no-useless-concat': 'warn',
|
||||||
|
'no-useless-constructor': 'warn',
|
||||||
|
'no-useless-rename': 'warn',
|
||||||
|
'no-useless-return': 'warn',
|
||||||
|
'no-var': 'warn',
|
||||||
|
'no-void': 'warn',
|
||||||
|
'no-whitespace-before-property': 'warn',
|
||||||
|
'prefer-numeric-literals': 'warn',
|
||||||
|
'prefer-object-spread': 'warn',
|
||||||
|
'prefer-regex-literals': 'warn',
|
||||||
|
'prefer-spread': 'warn',
|
||||||
|
'rest-spread-spacing': ['warn', 'never'],
|
||||||
|
'semi-spacing': 'warn',
|
||||||
|
'semi-style': ['warn', 'last'],
|
||||||
|
'space-unary-ops': ['warn', { words: true, nonwords: false }],
|
||||||
|
'switch-colon-spacing': 'warn',
|
||||||
|
'symbol-description': 'warn',
|
||||||
|
'template-curly-spacing': ['warn', 'never'],
|
||||||
|
'unicode-bom': ['warn', 'never'],
|
||||||
|
'use-isnan': ['warn', { enforceForSwitchCase: true, enforceForIndexOf: true }],
|
||||||
|
'valid-typeof': ['warn', { requireStringLiterals: true }],
|
||||||
|
'wrap-iife': ['warn', 'inside'],
|
||||||
|
'arrow-parens': ['warn', 'as-needed', { requireForBlockBody: false }],
|
||||||
|
'capitalized-comments': ['warn', 'always', {
|
||||||
|
ignoreConsecutiveComments: true,
|
||||||
|
ignorePattern: 'noinspection',
|
||||||
|
}],
|
||||||
|
'comma-spacing': 'warn',
|
||||||
|
'dot-notation': 'warn',
|
||||||
|
indent: ['warn', 2, { SwitchCase: 1 }],
|
||||||
|
'key-spacing': 'warn',
|
||||||
|
'keyword-spacing': ['warn', { overrides: { catch: { before: true, after: false } } }],
|
||||||
|
'max-len': ['warn', {
|
||||||
|
code: 180,
|
||||||
|
ignoreTrailingComments: true,
|
||||||
|
ignoreUrls: true,
|
||||||
|
ignoreStrings: true,
|
||||||
|
ignoreTemplateLiterals: true,
|
||||||
|
}],
|
||||||
|
'no-extra-boolean-cast': ['warn', { enforceForLogicalOperands: true }],
|
||||||
|
'no-multi-spaces': ['warn', { ignoreEOLComments: true }],
|
||||||
|
'no-tabs': 'warn',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
'no-trailing-spaces': 'warn',
|
||||||
|
'no-useless-escape': 'warn',
|
||||||
|
'nonblock-statement-body-position': ['warn', 'beside'],
|
||||||
|
'one-var': ['warn', 'never'],
|
||||||
|
'operator-linebreak': ['warn', 'before', {
|
||||||
|
overrides: { '=': 'after', '+=': 'after', '-=': 'after' },
|
||||||
|
}],
|
||||||
|
'prefer-template': 'warn',
|
||||||
|
'quote-props': ['warn', 'as-needed', { keywords: false }],
|
||||||
|
quotes: ['warn', 'double', { avoidEscape: true, allowTemplateLiterals: false }],
|
||||||
|
semi: ["error", "never"],
|
||||||
|
'spaced-comment': 'warn',
|
||||||
|
'jsdoc/check-access': 'warn',
|
||||||
|
'jsdoc/check-alignment': 'warn',
|
||||||
|
'jsdoc/check-examples': 'off',
|
||||||
|
'jsdoc/check-indentation': 'off',
|
||||||
|
'jsdoc/check-line-alignment': 'off',
|
||||||
|
'jsdoc/check-param-names': 'warn',
|
||||||
|
'jsdoc/check-property-names': 'warn',
|
||||||
|
'jsdoc/check-syntax': 'off',
|
||||||
|
'jsdoc/check-tag-names': ['warn', { definedTags: ['category'] }],
|
||||||
|
'jsdoc/check-types': 'warn',
|
||||||
|
'jsdoc/check-values': 'warn',
|
||||||
|
'jsdoc/empty-tags': 'warn',
|
||||||
|
'jsdoc/implements-on-classes': 'warn',
|
||||||
|
'jsdoc/match-description': 'off',
|
||||||
|
'jsdoc/newline-after-description': 'off',
|
||||||
|
'jsdoc/no-bad-blocks': 'warn',
|
||||||
|
'jsdoc/no-defaults': 'off',
|
||||||
|
'jsdoc/no-types': 'off',
|
||||||
|
'jsdoc/no-undefined-types': 'off',
|
||||||
|
'jsdoc/require-description': 'warn',
|
||||||
|
'jsdoc/require-description-complete-sentence': 'off',
|
||||||
|
'jsdoc/require-example': 'off',
|
||||||
|
'jsdoc/require-file-overview': 'off',
|
||||||
|
'jsdoc/require-hyphen-before-param-description': ['warn', 'never'],
|
||||||
|
'jsdoc/require-jsdoc': 'warn',
|
||||||
|
'jsdoc/require-param': 'warn',
|
||||||
|
'jsdoc/require-param-description': 'off',
|
||||||
|
'jsdoc/require-param-name': 'warn',
|
||||||
|
'jsdoc/require-param-type': 'warn',
|
||||||
|
'jsdoc/require-property': 'warn',
|
||||||
|
'jsdoc/require-property-description': 'off',
|
||||||
|
'jsdoc/require-property-name': 'warn',
|
||||||
|
'jsdoc/require-property-type': 'warn',
|
||||||
|
'jsdoc/require-returns': 'off',
|
||||||
|
'jsdoc/require-returns-check': 'warn',
|
||||||
|
'jsdoc/require-returns-description': 'off',
|
||||||
|
'jsdoc/require-returns-type': 'warn',
|
||||||
|
'jsdoc/require-throws': 'off',
|
||||||
|
'jsdoc/require-yields': 'warn',
|
||||||
|
'jsdoc/require-yields-check': 'warn',
|
||||||
|
'jsdoc/valid-types': 'off',
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
jsdoc: {
|
||||||
|
preferredTypes: {
|
||||||
|
'.<>': '<>',
|
||||||
|
object: 'Object',
|
||||||
|
Object: 'object',
|
||||||
|
},
|
||||||
|
mode: 'typescript',
|
||||||
|
tagNamePreference: {
|
||||||
|
augments: 'extends',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Ajout de la configuration Prettier qui désactive les règles ESLint en conflit avec Prettier
|
||||||
|
configPrettier
|
||||||
|
];
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const gulp = require('gulp');
|
||||||
|
const less = require('gulp-less');
|
||||||
|
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
/* Compile LESS
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
function compileLESS() {
|
||||||
|
return gulp.src("styles/fvtt-prism-rpg.less")
|
||||||
|
.pipe(less())
|
||||||
|
.pipe(gulp.dest("./css"))
|
||||||
|
}
|
||||||
|
const css = gulp.series(compileLESS);
|
||||||
|
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
/* Watch Updates
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
const SIMPLE_LESS = ["styles/*.less"];
|
||||||
|
|
||||||
|
function watchUpdates() {
|
||||||
|
gulp.watch(SIMPLE_LESS, css);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
/* Export Tasks
|
||||||
|
/* ----------------------------------------- */
|
||||||
|
|
||||||
|
exports.default = gulp.series(
|
||||||
|
gulp.parallel(css),
|
||||||
|
watchUpdates
|
||||||
|
);
|
||||||
|
exports.css = css;
|
||||||
|
exports.watchUpdates = watchUpdates;
|
||||||
|
|
||||||
@@ -0,0 +1,879 @@
|
|||||||
|
{
|
||||||
|
"COMBAT": {
|
||||||
|
"Round": "Second {round}",
|
||||||
|
"Rounds": "Seconds",
|
||||||
|
"RoundNext": "Next second"
|
||||||
|
},
|
||||||
|
"PRISMRPG": {
|
||||||
|
"Armor": {
|
||||||
|
"Category": {
|
||||||
|
"heavy": "Heavy",
|
||||||
|
"light": "Light",
|
||||||
|
"medium": "Medium"
|
||||||
|
},
|
||||||
|
"FIELDS": {
|
||||||
|
"armorType": {
|
||||||
|
"label": "Category"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"damageReduction": {
|
||||||
|
"label": "Damage reduction"
|
||||||
|
},
|
||||||
|
"defense": {
|
||||||
|
"label": "Defense"
|
||||||
|
},
|
||||||
|
"encLoad": {
|
||||||
|
"label": "Load"
|
||||||
|
},
|
||||||
|
"equipped": {
|
||||||
|
"label": "Equipped?"
|
||||||
|
},
|
||||||
|
"hp": {
|
||||||
|
"label": "HP"
|
||||||
|
},
|
||||||
|
"isHelmet": {
|
||||||
|
"label": "Is Helmet?"
|
||||||
|
},
|
||||||
|
"maximumMovement": {
|
||||||
|
"label": "Maximum movement"
|
||||||
|
},
|
||||||
|
"money": {
|
||||||
|
"label": "Money unit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Character": {
|
||||||
|
"FIELDS": {
|
||||||
|
"moneys": {
|
||||||
|
"tinbit": {
|
||||||
|
"label": "Tin Bits",
|
||||||
|
"value": {
|
||||||
|
"label": "Tin Bits"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"silver": {
|
||||||
|
"label": "Silver",
|
||||||
|
"value": {
|
||||||
|
"label": "Silver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"copper": {
|
||||||
|
"label": "Copper",
|
||||||
|
"value": {
|
||||||
|
"label": "Copper"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gold": {
|
||||||
|
"label": "Gold",
|
||||||
|
"value": {
|
||||||
|
"label": "Gold"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"platinum": {
|
||||||
|
"label": "Platinum",
|
||||||
|
"value": {
|
||||||
|
"label": "Platinum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"label": "Appearance"
|
||||||
|
},
|
||||||
|
"challenges": {
|
||||||
|
"agility": {
|
||||||
|
"label": "Dexterity"
|
||||||
|
},
|
||||||
|
"dying": {
|
||||||
|
"label": "Dying"
|
||||||
|
},
|
||||||
|
"str": {
|
||||||
|
"label": "Strength"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"developmentPoints": {
|
||||||
|
"label": "Development points",
|
||||||
|
"total": {
|
||||||
|
"label": "Total"
|
||||||
|
},
|
||||||
|
"remaining": {
|
||||||
|
"label": "Remaining"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"char": {
|
||||||
|
"label": "Charisma"
|
||||||
|
},
|
||||||
|
"combat": {
|
||||||
|
"armorHitPoints": {
|
||||||
|
"label": "Armor hit points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"con": {
|
||||||
|
"label": "Constitution"
|
||||||
|
},
|
||||||
|
"dex": {
|
||||||
|
"label": "Dexterity"
|
||||||
|
},
|
||||||
|
"int": {
|
||||||
|
"label": "Intelligence"
|
||||||
|
},
|
||||||
|
"perception": {
|
||||||
|
"bonus": {
|
||||||
|
"label": "Bonus"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"label": "Perception"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"saves": {
|
||||||
|
"contagion": {
|
||||||
|
"label": "Contagion"
|
||||||
|
},
|
||||||
|
"dodge": {
|
||||||
|
"label": "Dodge"
|
||||||
|
},
|
||||||
|
"pain": {
|
||||||
|
"label": "Pain"
|
||||||
|
},
|
||||||
|
"poison": {
|
||||||
|
"label": "Poison"
|
||||||
|
},
|
||||||
|
"toughness": {
|
||||||
|
"label": "Toughness"
|
||||||
|
},
|
||||||
|
"will": {
|
||||||
|
"label": "Will"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"str": {
|
||||||
|
"label": "Strength"
|
||||||
|
},
|
||||||
|
"wis": {
|
||||||
|
"label": "Wisdom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Monster": {
|
||||||
|
"FIELDS": {
|
||||||
|
"resists": {
|
||||||
|
"resistTorture": {
|
||||||
|
"label": "Resist torture"
|
||||||
|
},
|
||||||
|
"resistPerformance": {
|
||||||
|
"label": "Resist performance"
|
||||||
|
},
|
||||||
|
"resistIntimidation": {
|
||||||
|
"label": "Resist intimidation"
|
||||||
|
},
|
||||||
|
"perception": {
|
||||||
|
"label": "Perception"
|
||||||
|
},
|
||||||
|
"stealth": {
|
||||||
|
"label": "Stealth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"painCourage": {
|
||||||
|
"label": "Pain/Courage"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"label": "Appearance"
|
||||||
|
},
|
||||||
|
"challenges": {
|
||||||
|
"agility": {
|
||||||
|
"label": "Dexterity"
|
||||||
|
},
|
||||||
|
"dying": {
|
||||||
|
"label": "Dying"
|
||||||
|
},
|
||||||
|
"str": {
|
||||||
|
"label": "Strength"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"developmentPoints": {
|
||||||
|
"label": "Development points",
|
||||||
|
"total": {
|
||||||
|
"label": "Total"
|
||||||
|
},
|
||||||
|
"remaining": {
|
||||||
|
"label": "Remaining"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"char": {
|
||||||
|
"label": "Charisma"
|
||||||
|
},
|
||||||
|
"combat": {
|
||||||
|
"armorHitPoints": {
|
||||||
|
"label": "Armor hit points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"con": {
|
||||||
|
"label": "Constitution"
|
||||||
|
},
|
||||||
|
"dex": {
|
||||||
|
"label": "Dexterity"
|
||||||
|
},
|
||||||
|
"int": {
|
||||||
|
"label": "Intelligence"
|
||||||
|
},
|
||||||
|
"perception": {
|
||||||
|
"bonus": {
|
||||||
|
"label": "Bonus"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"label": "Perception"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"saves": {
|
||||||
|
"contagion": {
|
||||||
|
"label": "Contagion"
|
||||||
|
},
|
||||||
|
"dodge": {
|
||||||
|
"label": "Dodge"
|
||||||
|
},
|
||||||
|
"pain": {
|
||||||
|
"label": "Pain"
|
||||||
|
},
|
||||||
|
"poison": {
|
||||||
|
"label": "Poison"
|
||||||
|
},
|
||||||
|
"toughness": {
|
||||||
|
"label": "Toughness"
|
||||||
|
},
|
||||||
|
"will": {
|
||||||
|
"label": "Will"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"str": {
|
||||||
|
"label": "Strength"
|
||||||
|
},
|
||||||
|
"wis": {
|
||||||
|
"label": "Wisdom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Delete": "Delete",
|
||||||
|
"Edit": "Edit",
|
||||||
|
"Equipment": {
|
||||||
|
"FIELDS": {
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"encLoad": {
|
||||||
|
"label": "Load"
|
||||||
|
},
|
||||||
|
"money": {
|
||||||
|
"label": "Money unit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Gift": {
|
||||||
|
"FIELDS": {
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Label": {
|
||||||
|
"agility": "Dexterity",
|
||||||
|
"gotoToken": "Go to token",
|
||||||
|
"combatAction": "Combat action",
|
||||||
|
"currentAction": "Current ongoing action",
|
||||||
|
"selectAction": "Select an action",
|
||||||
|
"spell-power": "Spell - Power",
|
||||||
|
"spell-attack": "Spell - Attack",
|
||||||
|
"miracle-power": "Miracle - Power",
|
||||||
|
"miracle-attack": "Miracle - Attack",
|
||||||
|
"spell": "Spell",
|
||||||
|
"will":"Will",
|
||||||
|
"dodge":"Dodge",
|
||||||
|
"toughness":"Toughness",
|
||||||
|
"contagion":"Contagion",
|
||||||
|
"poison":"Poison",
|
||||||
|
"pain":"Pain",
|
||||||
|
"paincourage":"Pain/Courage",
|
||||||
|
"granted": "Granted Dice",
|
||||||
|
"shields": "Shields",
|
||||||
|
"armorHitPoints": "Armor hit points",
|
||||||
|
"grantedAttackDice": "Granted attack",
|
||||||
|
"grantedDamageDice": "Granted damage",
|
||||||
|
"grantedDefenseDice": "Granted defense",
|
||||||
|
"damageResistance": "Damage resistance",
|
||||||
|
"damageResistanceShort": "DR",
|
||||||
|
"stealth": "Stealth",
|
||||||
|
"progressionDice": "Progression/Lethargy dice",
|
||||||
|
"rollProgressionCount": "Roll progression count",
|
||||||
|
"rollProgressionDice": "Roll progression/Lethargy dice",
|
||||||
|
"earned": "Earned",
|
||||||
|
"divinityPoints": "Divinity points",
|
||||||
|
"aetherPoints": "Aether points",
|
||||||
|
"attacks": "Attacks",
|
||||||
|
"monster": "Monster",
|
||||||
|
"Resist" :"Resist",
|
||||||
|
"resist": "Resist",
|
||||||
|
"resistTorture": "Resist torture",
|
||||||
|
"resistPerformance": "Resist performance",
|
||||||
|
"resistIntimidation": "Resist intimidation",
|
||||||
|
"initiative": "Initiative",
|
||||||
|
"maxInitiativeWisdom": "Max initiative (from wisdom)",
|
||||||
|
"combat": "Combat",
|
||||||
|
"rollInitiative": "Roll initiative",
|
||||||
|
"money": "Money",
|
||||||
|
"favorResult": "Favor result",
|
||||||
|
"disfavorResult": "Disfavor result",
|
||||||
|
"otherResult": "Other result",
|
||||||
|
"disfavor": "Disfavor",
|
||||||
|
"favor": "Favor",
|
||||||
|
"wounds": "Wounds",
|
||||||
|
"name": "Name",
|
||||||
|
"hp": "HP",
|
||||||
|
"duration": "Duration",
|
||||||
|
"combatDetails": "Combat details",
|
||||||
|
"Challenges": "Challenges",
|
||||||
|
"HP": "HP",
|
||||||
|
"Movement": "Movement",
|
||||||
|
"Saves": "Saves",
|
||||||
|
"app": "APP",
|
||||||
|
"armor": "Armor",
|
||||||
|
"armors": "Armors",
|
||||||
|
"baseModifier": "Base modifier",
|
||||||
|
"biodata": "Biodata",
|
||||||
|
"biography": "Biography",
|
||||||
|
"bonus": "Bonus",
|
||||||
|
"cha": "CHA",
|
||||||
|
"challenge": "Challenge",
|
||||||
|
"challenges": {
|
||||||
|
"agility": "Dexterity",
|
||||||
|
"dying": "Dying",
|
||||||
|
"strength": "Strength"
|
||||||
|
},
|
||||||
|
"characteristic": "Characteristic",
|
||||||
|
"characteristics": "Characteristics",
|
||||||
|
"con": "CON",
|
||||||
|
"damage": "Damage",
|
||||||
|
"description": "Description",
|
||||||
|
"details": "Details",
|
||||||
|
"dex": "DEX",
|
||||||
|
"equipment": "Equipment",
|
||||||
|
"experience": "Experience",
|
||||||
|
"gifts": "Gifts",
|
||||||
|
"grit": "Grit",
|
||||||
|
"gritEarned": "Grit earned",
|
||||||
|
"int": "INT",
|
||||||
|
"jet": "Roll",
|
||||||
|
"level": "Level",
|
||||||
|
"luc": "LUC",
|
||||||
|
"luck": "Luck",
|
||||||
|
"luckEarned": "Luck earned",
|
||||||
|
"malus": "Malus",
|
||||||
|
"maximum": "Maximum",
|
||||||
|
"miracles": "Miracles",
|
||||||
|
"movement": {
|
||||||
|
"jog": "Jog",
|
||||||
|
"run": "Run",
|
||||||
|
"sprint": "Sprint",
|
||||||
|
"walk": "Walk",
|
||||||
|
"jumpBroad": "Broad jump",
|
||||||
|
"jumpRunning": "Running jump",
|
||||||
|
"jumpVertical": "Vertical jump"
|
||||||
|
},
|
||||||
|
"newArmor": "New armor",
|
||||||
|
"newWeapon": "New weapon",
|
||||||
|
"notes": "Notes",
|
||||||
|
"pc": "PC",
|
||||||
|
"perception": "Perception",
|
||||||
|
"rangeDefenseDialog": "Ranged defense dialog",
|
||||||
|
"rangeDefenseRoll": "Ranged defense roll",
|
||||||
|
"rangedAttackDefense": "Ranged attack defense",
|
||||||
|
"resource": "Resource",
|
||||||
|
"resources": "Resources",
|
||||||
|
"save": "Save",
|
||||||
|
"saves": {
|
||||||
|
"contagion": "Contagion",
|
||||||
|
"dodge": "Dodge",
|
||||||
|
"pain": "Pain",
|
||||||
|
"poison": "Poison",
|
||||||
|
"toughness": "Toughness",
|
||||||
|
"will": "Will",
|
||||||
|
"paincourage": "Pain/Courage"
|
||||||
|
},
|
||||||
|
"skill": "Skill",
|
||||||
|
"skillBonus": "Skill bonus",
|
||||||
|
"skills": "Skills",
|
||||||
|
"spells": "Spells",
|
||||||
|
"str": "STR",
|
||||||
|
"titleChallenge": "Challenge",
|
||||||
|
"titleSave": "Save",
|
||||||
|
"titleSkill": "Skill",
|
||||||
|
"total": "Total",
|
||||||
|
"vulnerabilities": "Vulnerabilities",
|
||||||
|
"weapon": "Weapon",
|
||||||
|
"weapon-attack": "Weapon attack",
|
||||||
|
"weapon-damage": "Weapon damage",
|
||||||
|
"weapon-defense": "Weapon defense",
|
||||||
|
"monster-attack": "Monster attack",
|
||||||
|
"monster-damage": "Monster damage",
|
||||||
|
"monster-defense": "Monster defense",
|
||||||
|
"weapons": "Weapons",
|
||||||
|
"wis": "WIS"
|
||||||
|
},
|
||||||
|
"Miracle": {
|
||||||
|
"FIELDS": {
|
||||||
|
"miracleType": {
|
||||||
|
"label": "Miracle Type"
|
||||||
|
},
|
||||||
|
"attackRoll": {
|
||||||
|
"label": "Attack roll"
|
||||||
|
},
|
||||||
|
"powerRoll": {
|
||||||
|
"label": "Power roll"
|
||||||
|
},
|
||||||
|
"materialComponent": {
|
||||||
|
"label": "Material component"
|
||||||
|
},
|
||||||
|
"catalyst": {
|
||||||
|
"label": "Catalyst"
|
||||||
|
},
|
||||||
|
"areaAffected": {
|
||||||
|
"label": "Area affected"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"catalyst": {
|
||||||
|
"label": "Catalyst"
|
||||||
|
},
|
||||||
|
"material": {
|
||||||
|
"label": "Material"
|
||||||
|
},
|
||||||
|
"religious": {
|
||||||
|
"label": "Religious"
|
||||||
|
},
|
||||||
|
"somatic": {
|
||||||
|
"label": "Somatic"
|
||||||
|
},
|
||||||
|
"verbal": {
|
||||||
|
"label": "Verbal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"label": "Level"
|
||||||
|
},
|
||||||
|
"miracleRange": {
|
||||||
|
"label": "Range"
|
||||||
|
},
|
||||||
|
"prayerTime": {
|
||||||
|
"label": "Prayer time"
|
||||||
|
},
|
||||||
|
"savingThrow": {
|
||||||
|
"label": "Saving throw"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Money": {
|
||||||
|
"Coppers": "Copper",
|
||||||
|
"Golds": "Gold",
|
||||||
|
"Platinums": "Platinum",
|
||||||
|
"Silvers": "Silver",
|
||||||
|
"Tinbits": "Tin Bits"
|
||||||
|
},
|
||||||
|
"Notifications": {
|
||||||
|
"rollFromWeapon": "Roll from an equipped weapon",
|
||||||
|
"rollTypeNotFound": "Roll type not found",
|
||||||
|
"skillNotFound": "Skill not found",
|
||||||
|
"messageProgressionOK": "{name} can perform his action !",
|
||||||
|
"messageLethargyOK": "{spellName} : Lethargy ended ( dice result {roll}). <br>{name} can perform a new action !",
|
||||||
|
"messageLethargyKO": "{spellName} : Lethargy still ongoing ... ( dice result : {roll} )",
|
||||||
|
"messageProgressionKO": "{name} can't attack this second.",
|
||||||
|
"messageProgressionOKMonster": "{name} can attack this second with {weapon}.",
|
||||||
|
"messageProgressionKOMonster": "{name} can't attack this second (dice result {roll})."
|
||||||
|
},
|
||||||
|
"Opponent": {
|
||||||
|
"FIELDS": {}
|
||||||
|
},
|
||||||
|
"Roll": {
|
||||||
|
"changeDice": "Change dice",
|
||||||
|
"failure": "Failure",
|
||||||
|
"modifier": "Modifier",
|
||||||
|
"modifierBonusMalus": "Modifier bonus/malus",
|
||||||
|
"normal": "Normal",
|
||||||
|
"roll": "Roll",
|
||||||
|
"save": "Save roll {save}",
|
||||||
|
"success": "Success",
|
||||||
|
"visibility": "Visibility",
|
||||||
|
"favorDisfavor": "Favor/Disfavor"
|
||||||
|
},
|
||||||
|
"Save": {
|
||||||
|
"FIELDS": {
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"label": "Value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Setting": {
|
||||||
|
"displayOpponentMalus": "Afficher le malus d'adversité",
|
||||||
|
"displayOpponentMalusHint": "Affiche le malus d'adversité pour les joueurs.",
|
||||||
|
"fortune": "Roue de Fortune",
|
||||||
|
"fortuneHint": "Valeur de la roue de Fortune. Nombre de joueurs + 1 en début de partie."
|
||||||
|
},
|
||||||
|
"Shield": {
|
||||||
|
"FIELDS": {
|
||||||
|
"autodestruction": {
|
||||||
|
"bashing": {
|
||||||
|
"label": "Bashing"
|
||||||
|
},
|
||||||
|
"piercing": {
|
||||||
|
"label": "Piercing"
|
||||||
|
},
|
||||||
|
"slashing": {
|
||||||
|
"label": "Slashing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"crouching": {
|
||||||
|
"max": {
|
||||||
|
"label": "Max"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"label": "Min"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"damagereduction": {
|
||||||
|
"label": "Damage reduction"
|
||||||
|
},
|
||||||
|
"defense": {
|
||||||
|
"label": "Defense"
|
||||||
|
},
|
||||||
|
"destruction": {
|
||||||
|
"bashing": {
|
||||||
|
"label": "Bashing"
|
||||||
|
},
|
||||||
|
"piercing": {
|
||||||
|
"label": "Piercing"
|
||||||
|
},
|
||||||
|
"slashing": {
|
||||||
|
"label": "Slashing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"encLoad": {
|
||||||
|
"label": "Load"
|
||||||
|
},
|
||||||
|
"equipped": {
|
||||||
|
"label": "Equipped"
|
||||||
|
},
|
||||||
|
"hascover": {
|
||||||
|
"label": "Provides cover"
|
||||||
|
},
|
||||||
|
"hp": {
|
||||||
|
"label": "HP"
|
||||||
|
},
|
||||||
|
"money": {
|
||||||
|
"label": "Money unit"
|
||||||
|
},
|
||||||
|
"movementreduction": {
|
||||||
|
"label": "Movement reduction"
|
||||||
|
},
|
||||||
|
"standing": {
|
||||||
|
"max": {
|
||||||
|
"label": "Max"
|
||||||
|
},
|
||||||
|
"min": {
|
||||||
|
"label": "Min"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Skill": {
|
||||||
|
"Category": {
|
||||||
|
"armor": "Armor",
|
||||||
|
"layperson": "Layperson",
|
||||||
|
"professional": "Professional",
|
||||||
|
"resist": "Resist",
|
||||||
|
"weapon": "Weapon"
|
||||||
|
},
|
||||||
|
"FIELDS": {
|
||||||
|
"base": {
|
||||||
|
"label": "Base"
|
||||||
|
},
|
||||||
|
"bonus": {
|
||||||
|
"label": "Bonus"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"label": "Category"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"classesCost": {
|
||||||
|
"fighter": {
|
||||||
|
"label": "Fighter Cost"
|
||||||
|
},
|
||||||
|
"rogue": {
|
||||||
|
"label": "Rogue Cost"
|
||||||
|
},
|
||||||
|
"ranger": {
|
||||||
|
"label": "Ranger Cost"
|
||||||
|
},
|
||||||
|
"magicuser": {
|
||||||
|
"label": "Magic User Cost"
|
||||||
|
},
|
||||||
|
"cleric": {
|
||||||
|
"label": "Cleric Cost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"weaponBonus": {
|
||||||
|
"attack": {
|
||||||
|
"label": "Attack Bonus"
|
||||||
|
},
|
||||||
|
"damage": {
|
||||||
|
"label": "Damage Bonus"
|
||||||
|
},
|
||||||
|
"defense": {
|
||||||
|
"label": "Defense Bonus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weaponClass": {
|
||||||
|
"label": "Class"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Spell": {
|
||||||
|
"FIELDS": {
|
||||||
|
"criticalType": {
|
||||||
|
"label": "Crit. Type"
|
||||||
|
},
|
||||||
|
"attackRoll": {
|
||||||
|
"label": "Attack roll"
|
||||||
|
},
|
||||||
|
"powerRoll": {
|
||||||
|
"label": "Power roll"
|
||||||
|
},
|
||||||
|
"areaAffected": {
|
||||||
|
"label": "Area affected"
|
||||||
|
},
|
||||||
|
"castingTime": {
|
||||||
|
"label": "Casting time"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"material": {
|
||||||
|
"label": "Material"
|
||||||
|
},
|
||||||
|
"somatic": {
|
||||||
|
"label": "Somatic"
|
||||||
|
},
|
||||||
|
"verbal": {
|
||||||
|
"label": "Verbal"
|
||||||
|
},
|
||||||
|
"catalyst": {
|
||||||
|
"label": "Catalyst"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"memorized": {
|
||||||
|
"label": "Memorized"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"label": "Duration"
|
||||||
|
},
|
||||||
|
"extraAetherPoints": {
|
||||||
|
"label": "Extra aether points"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"label": "Level"
|
||||||
|
},
|
||||||
|
"savingThrow": {
|
||||||
|
"label": "Saving throw"
|
||||||
|
},
|
||||||
|
"spellRange": {
|
||||||
|
"label": "Range"
|
||||||
|
},
|
||||||
|
"materialComponent": {
|
||||||
|
"label": "Material component"
|
||||||
|
},
|
||||||
|
"catalyst": {
|
||||||
|
"label": "Catalyst"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ToggleSheet": "Toggle mode",
|
||||||
|
"Tooltip": {
|
||||||
|
"addEquipment": "New equipment",
|
||||||
|
"addSpell": "New spells",
|
||||||
|
"skill": "Skills list"
|
||||||
|
},
|
||||||
|
"Vulnerability": {
|
||||||
|
"FIELDS": {
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"label": "Description"
|
||||||
|
},
|
||||||
|
"gainedPoints": {
|
||||||
|
"label": "Gained points"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Warning": {},
|
||||||
|
"Weapon": {
|
||||||
|
"FIELDS": {
|
||||||
|
"isAgile": {
|
||||||
|
"label": "Is Agile?"
|
||||||
|
},
|
||||||
|
"combatProgressionDice": {
|
||||||
|
"label": "Combat progression dice"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"label": "Cost"
|
||||||
|
},
|
||||||
|
"damage": {
|
||||||
|
"damageM": {
|
||||||
|
"label": "Medium"
|
||||||
|
},
|
||||||
|
"damageS": {
|
||||||
|
"label": "Small"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"damageType": {
|
||||||
|
"typeB": {
|
||||||
|
"label": "Bashing"
|
||||||
|
},
|
||||||
|
"typeP": {
|
||||||
|
"label": "Piercing"
|
||||||
|
},
|
||||||
|
"typeS": {
|
||||||
|
"label": "Slashing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defense": {
|
||||||
|
"label": "Defense"
|
||||||
|
},
|
||||||
|
"defenseMax": {
|
||||||
|
"label": "Defense max"
|
||||||
|
},
|
||||||
|
"encLoad": {
|
||||||
|
"label": "Load"
|
||||||
|
},
|
||||||
|
"equipped": {
|
||||||
|
"label": "Equipped"
|
||||||
|
},
|
||||||
|
"hands": {
|
||||||
|
"label": "Hands"
|
||||||
|
},
|
||||||
|
"money": {
|
||||||
|
"label": "Money unit"
|
||||||
|
},
|
||||||
|
"secondsToAttack": {
|
||||||
|
"label": "Seconds to attack"
|
||||||
|
},
|
||||||
|
"speed": {
|
||||||
|
"carefulAim": {
|
||||||
|
"label": "Careful aim"
|
||||||
|
},
|
||||||
|
"focusedAim": {
|
||||||
|
"label": "Focused aim"
|
||||||
|
},
|
||||||
|
"simpleAim": {
|
||||||
|
"label": "Simple aim"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"applyStrengthDamageBonus": {
|
||||||
|
"label": "Apply strength damage bonus"
|
||||||
|
},
|
||||||
|
"bonuses": {
|
||||||
|
"attackBonus": {
|
||||||
|
"label": "Attack Bonus"
|
||||||
|
},
|
||||||
|
"damageBonus": {
|
||||||
|
"label": "Damage Bonus"
|
||||||
|
},
|
||||||
|
"defenseBonus": {
|
||||||
|
"label": "Defense Bonus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weaponRange": {
|
||||||
|
"extreme": {
|
||||||
|
"label": "Extreme"
|
||||||
|
},
|
||||||
|
"long": {
|
||||||
|
"label": "Long"
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"label": "Medium"
|
||||||
|
},
|
||||||
|
"outOfSkill": {
|
||||||
|
"label": "Out of skill"
|
||||||
|
},
|
||||||
|
"pointBlank": {
|
||||||
|
"label": "Point blank"
|
||||||
|
},
|
||||||
|
"short": {
|
||||||
|
"label": "Short"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weaponType": {
|
||||||
|
"label": "Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WeaponClass": {
|
||||||
|
"axe": "Axe",
|
||||||
|
"bow": "Bow",
|
||||||
|
"flail": "Flail",
|
||||||
|
"hammer": "Hammer",
|
||||||
|
"longblade": "Long blade",
|
||||||
|
"mace": "Mace",
|
||||||
|
"mediumblade": "Medium blade",
|
||||||
|
"polearm": "Polearm",
|
||||||
|
"shortblade": "Short blade",
|
||||||
|
"sling": "Sling",
|
||||||
|
"thrown": "Thrown",
|
||||||
|
"unarmed": "Unarmed"
|
||||||
|
},
|
||||||
|
"WeaponType": {
|
||||||
|
"melee": "Melee",
|
||||||
|
"ranged": "Ranged"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TYPES": {
|
||||||
|
"Actor": {
|
||||||
|
"character": "Character",
|
||||||
|
"monster": "Monster"
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"armor": "Armor",
|
||||||
|
"equipment": "Equipment",
|
||||||
|
"gift": "Gift",
|
||||||
|
"miracle": "Miracle",
|
||||||
|
"save": "Save",
|
||||||
|
"shield": "Shield",
|
||||||
|
"skill": "Skill",
|
||||||
|
"spell": "Spell",
|
||||||
|
"vulnerability": "Vulnerability",
|
||||||
|
"weapon": "Weapon"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export { default as PrismRPGCharacterSheet } from "./sheets/character-sheet.mjs";
|
||||||
|
export { default as PrismRPGMonsterSheet } from "./sheets/monster-sheet.mjs"
|
||||||
|
export { default as PrismRPGWeaponSheet } from "./sheets/weapon-sheet.mjs"
|
||||||
|
export { default as PrismRPGSkillSheet } from "./sheets/skill-sheet.mjs"
|
||||||
|
export { default as PrismRPGGiftSheet } from "./sheets/gift-sheet.mjs"
|
||||||
|
export { default as PrismRPGVulnerabilitySheet } from "./sheets/vulnerability-sheet.mjs"
|
||||||
|
export { default as PrismRPGArmorSheet } from "./sheets/armor-sheet.mjs"
|
||||||
|
export { default as PrismRPGSpellSheet } from "./sheets/spell-sheet.mjs"
|
||||||
|
export { default as PrismRPGEquipmentSheet } from "./sheets/equipment-sheet.mjs"
|
||||||
|
export { default as PrismRPGShieldSheet } from "./sheets/shield-sheet.mjs"
|
||||||
|
export { default as PrismRPGMiracleSheet } from "./sheets/miracle-sheet.mjs"
|
||||||
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
export class PrismRPGCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
"header": {
|
||||||
|
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-header-v2.hbs"
|
||||||
|
},
|
||||||
|
"tracker": {
|
||||||
|
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-v2.hbs"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"template": "systems/fvtt-prism-rpg/templates/combat-tracker-footer-v2.hbs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
|
||||||
|
actions: {
|
||||||
|
initiativePlus: PrismRPGCombatTracker.#initiativePlus,
|
||||||
|
initiativeMinus: PrismRPGCombatTracker.#initiativeMinus,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
let data = await super._prepareContext(options);
|
||||||
|
console?.log("Combat Tracker Data", data);
|
||||||
|
/*for (let u of data.turns) {
|
||||||
|
let c = game.combat.combatants.get(u.id);
|
||||||
|
u.progressionCount = c.system.progressionCount
|
||||||
|
u.isMonster = c.actor.type === "monster"
|
||||||
|
}
|
||||||
|
console.log("Combat Data", data);*/
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #initiativePlus(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let cId = ev.target.closest(".combatant").dataset.combatantId;
|
||||||
|
let c = game.combat.combatants.get(cId);
|
||||||
|
c.update({ 'initiative': c.initiative + 1 });
|
||||||
|
console.log("Initiative Plus");
|
||||||
|
}
|
||||||
|
|
||||||
|
static #initiativeMinus(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let cId = ev.target.closest(".combatant").dataset.combatantId;
|
||||||
|
let c = game.combat.combatants.get(cId);
|
||||||
|
let newInit = Math.max(c.initiative - 1, 0);
|
||||||
|
c.update({ 'initiative': newInit });
|
||||||
|
}
|
||||||
|
|
||||||
|
activateListeners(html) {
|
||||||
|
super.activateListeners(html);
|
||||||
|
// Display Combat settings
|
||||||
|
html.find(".initiative-plus").click(ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
|
||||||
|
let c = game.combat.combatants.get(cId);
|
||||||
|
c.update({ 'initiative': c.initiative + 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
html.find(".initiative-minus").click(ev => {
|
||||||
|
ev.preventDefault();
|
||||||
|
let cId = ev.currentTarget.closest(".combatant").dataset.combatantId;
|
||||||
|
let c = game.combat.combatants.get(cId);
|
||||||
|
c.update({ 'initiative': c.initiative - 1 });
|
||||||
|
console.log("Initiative Minus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static get defaultOptions() {
|
||||||
|
let path = "systems/fvtt-prism-rpg/templates/combat-tracker.hbs";
|
||||||
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
||||||
|
template: path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PrismRPGCombat extends Combat {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the Array of combatants sorted into initiative order, breaking ties alphabetically by name.
|
||||||
|
* @returns {Combatant[]}
|
||||||
|
*/
|
||||||
|
setupTurns() {
|
||||||
|
console?.log("Setup Turns....");
|
||||||
|
this.turns ||= [];
|
||||||
|
|
||||||
|
// Determine the turn order and the current turn
|
||||||
|
const turns = this.combatants.contents.sort(this.sortCombatantsLF);
|
||||||
|
if (this.turn !== null) this.turn = Math.clamp(this.turn, 0, turns.length - 1);
|
||||||
|
|
||||||
|
// Update state tracking
|
||||||
|
let c = turns[this.turn];
|
||||||
|
this.current = this._getCurrentState(c);
|
||||||
|
if (!this.previous) this.previous = this.current;
|
||||||
|
|
||||||
|
// Return the array of prepared turns
|
||||||
|
return this.turns = turns;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollInitiative(ids, options) {
|
||||||
|
console.log("%%%%%%%%% Roll Initiative", ids, options);
|
||||||
|
|
||||||
|
ids = typeof ids === "string" ? [ids] : ids;
|
||||||
|
let messages = [];
|
||||||
|
let rollMode = game.settings.get("core", "rollMode");
|
||||||
|
|
||||||
|
let updates = [];
|
||||||
|
for (let cId of ids) {
|
||||||
|
const c = this.combatants.get(cId);
|
||||||
|
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
|
||||||
|
if (user?.hasPlayerOwner) {
|
||||||
|
console.log("Rolling initiative for", c.actor.name);
|
||||||
|
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollInitiative", actorId: c.actor.id, combatId: this.id, combatantId: c.id });
|
||||||
|
} else {
|
||||||
|
user = game.users.find(u => u.active && u.isGM);
|
||||||
|
c.actor.system.rollInitiative(this.id, c.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetProgression(cId) {
|
||||||
|
let c = this.combatants.get(cId);
|
||||||
|
c.update({ 'system.progressionCount': 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
setCasting(cId) {
|
||||||
|
let c = this.combatants.get(cId);
|
||||||
|
c.setFlag(SYSTEM.id, "casting", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCasting(cId) {
|
||||||
|
let c = this.combatants.get(cId);
|
||||||
|
c.setFlag(SYSTEM.id, "casting", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
isCasting(cId) {
|
||||||
|
let c = this.combatants.get(cId);
|
||||||
|
return c.getFlag(SYSTEM.id, "casting");
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextTurn() {
|
||||||
|
console.log("NEXT TURN");
|
||||||
|
|
||||||
|
let turn = this.turn ?? -1;
|
||||||
|
let skipDefeated = this.settings.skipDefeated;
|
||||||
|
|
||||||
|
// Determine the next turn number
|
||||||
|
let next = null;
|
||||||
|
for (let [i, t] of this.turns.entries()) {
|
||||||
|
console.log("Turn", t);
|
||||||
|
if (i <= turn) continue;
|
||||||
|
if (skipDefeated && t.isDefeated) continue;
|
||||||
|
next = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe advance to the next round
|
||||||
|
let round = this.round;
|
||||||
|
if ((this.round === 0) || (next === null) || (next >= this.turns.length)) {
|
||||||
|
return this.nextRound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the document, passing data through a hook first
|
||||||
|
const updateData = { round, turn: next };
|
||||||
|
const updateOptions = { advanceTime: CONFIG.time.turnTime, direction: 1 };
|
||||||
|
Hooks.callAll("combatTurn", this, updateData, updateOptions);
|
||||||
|
return this.update(updateData, updateOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextRound() {
|
||||||
|
this.turnsDone = false
|
||||||
|
|
||||||
|
let turn = this.turn === null ? null : 0; // Preserve the fact that it's no-one's turn currently.
|
||||||
|
console.log("ROUND", this);
|
||||||
|
|
||||||
|
let advanceTime = Math.max(this.turns.length - this.turn, 0) * CONFIG.time.turnTime;
|
||||||
|
advanceTime += CONFIG.time.roundTime;
|
||||||
|
let nextRound = this.round + 1;
|
||||||
|
|
||||||
|
let initOK = true;
|
||||||
|
for (let c of this.combatants) {
|
||||||
|
if (c.initiative === null) {
|
||||||
|
initOK = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!initOK) {
|
||||||
|
ui.notifications.error("All combatants must have initiative rolled before the round can advance.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let c of this.combatants) {
|
||||||
|
if (nextRound >= c.initiative) {
|
||||||
|
let user = game.users.find(u => u.active && u.character && u.character.id === c.actor.id);
|
||||||
|
if (user?.hasPlayerOwner) {
|
||||||
|
game.socket.emit(`system.${SYSTEM.id}`, { type: "rollProgressionDice", progressionCount: c.system.progressionCount + 1, actorId: c.actor.id, combatId: this.id, combatantId: c.id });
|
||||||
|
} else {
|
||||||
|
user = game.users.find(u => u.active && u.isGM);
|
||||||
|
c.actor.system.rollProgressionDice(this.id, c.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the document, passing data through a hook first
|
||||||
|
const updateData = { round: nextRound, turn };
|
||||||
|
const updateOptions = { advanceTime, direction: 1 };
|
||||||
|
Hooks.callAll("combatRound", this, updateData, updateOptions);
|
||||||
|
return this.update(updateData, updateOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sortCombatantsLF(a, b) {
|
||||||
|
return a.initiative - b.initiative;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGArmorSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["armor"],
|
||||||
|
position: {
|
||||||
|
width: 400,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["armor-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/armor.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
export default class PrismRPGActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||||
|
/**
|
||||||
|
* Different sheet modes.r
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["prismrpg", "actor"],
|
||||||
|
position: {
|
||||||
|
width: 1400,
|
||||||
|
height: "auto",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
|
||||||
|
actions: {
|
||||||
|
editImage: PrismRPGActorSheet.#onEditImage,
|
||||||
|
toggleSheet: PrismRPGActorSheet.#onToggleSheet,
|
||||||
|
edit: PrismRPGActorSheet.#onItemEdit,
|
||||||
|
delete: PrismRPGActorSheet.#onItemDelete,
|
||||||
|
createSpell: PrismRPGActorSheet.#onCreateSpell,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current sheet mode.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the sheet currently in 'Play' mode?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isPlayMode() {
|
||||||
|
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the sheet currently in 'Edit' mode?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isEditMode() {
|
||||||
|
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = {
|
||||||
|
fields: this.document.schema.fields,
|
||||||
|
systemFields: this.document.system.schema.fields,
|
||||||
|
actor: this.document,
|
||||||
|
system: this.document.system,
|
||||||
|
source: this.document.toObject(),
|
||||||
|
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true }),
|
||||||
|
isEditMode: this.isEditMode,
|
||||||
|
isPlayMode: this.isPlayMode,
|
||||||
|
isEditable: this.isEditable,
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_onRender(context, options) {
|
||||||
|
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||||
|
// Add listeners to rollable elements
|
||||||
|
const rollables = this.element.querySelectorAll(".rollable")
|
||||||
|
rollables.forEach((d) => d.addEventListener("click", this._onRoll.bind(this)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region Drag-and-Drop Workflow
|
||||||
|
/**
|
||||||
|
* Create drag-and-drop workflow handlers for this Application
|
||||||
|
* @returns {DragDrop[]} An array of DragDrop handlers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
return this.options.dragDrop.map((d) => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this),
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this),
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is dropped on a target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _onDrop(event) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define whether a user is able to begin a dragstart workflow for a given drag selector
|
||||||
|
* @param {string} selector The candidate HTML selector for dragging
|
||||||
|
* @returns {boolean} Can the current user drag this selector?
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_canDragStart(selector) {
|
||||||
|
return this.isEditable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
|
||||||
|
* @param {string} selector The candidate HTML selector for the drop target
|
||||||
|
* @returns {boolean} Can the current user drop on this selector?
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_canDragDrop(selector) {
|
||||||
|
return this.isEditable && this.document.isOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur at the beginning of a drag start workflow.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_onDragStart(event) {
|
||||||
|
if ("link" in event.target.dataset) return
|
||||||
|
|
||||||
|
const el = event.currentTarget.closest('[data-drag="true"]')
|
||||||
|
const dragType = el?.dataset?.dragType
|
||||||
|
|
||||||
|
let dragData = {}
|
||||||
|
|
||||||
|
let target
|
||||||
|
switch (dragType) {
|
||||||
|
case "save":
|
||||||
|
target = event.currentTarget.querySelector("input")
|
||||||
|
dragData = {
|
||||||
|
actorId: this.document.id,
|
||||||
|
type: "roll",
|
||||||
|
rollType: target.dataset.rollType,
|
||||||
|
rollTarget: target.dataset.rollTarget,
|
||||||
|
value: target.value,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "resource":
|
||||||
|
target = event.currentTarget.querySelector("select")
|
||||||
|
dragData = {
|
||||||
|
actorId: this.document.id,
|
||||||
|
type: "roll",
|
||||||
|
rollType: target.dataset.rollType,
|
||||||
|
rollTarget: target.dataset.rollTarget,
|
||||||
|
value: target.value,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "damage":
|
||||||
|
dragData = {
|
||||||
|
actorId: this.document.id,
|
||||||
|
type: "rollDamage",
|
||||||
|
rollType: el.dataset.dragType,
|
||||||
|
rollTarget: el.dataset.itemId,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "attack":
|
||||||
|
dragData = {
|
||||||
|
actorId: this.document.id,
|
||||||
|
type: "rollAttack",
|
||||||
|
rollValue: el.dataset.rollValue,
|
||||||
|
rollTarget: el.dataset.rollTarget,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// Handle other cases or do nothing
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the data you need
|
||||||
|
|
||||||
|
if (!dragData) return
|
||||||
|
|
||||||
|
// Set data transfer
|
||||||
|
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is over a drop target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_onDragOver(event) { }
|
||||||
|
|
||||||
|
async _onDropItem(item) {
|
||||||
|
let itemData = item.toObject()
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Actions
|
||||||
|
/**
|
||||||
|
* Handle toggling between Edit and Play mode.
|
||||||
|
* @param {Event} event The initiating click event.
|
||||||
|
* @param {HTMLElement} target The current target of the event listener.
|
||||||
|
*/
|
||||||
|
static #onToggleSheet(event, target) {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle changing a Document's image.
|
||||||
|
*
|
||||||
|
* @this PrismRPGCharacterSheet
|
||||||
|
* @param {PointerEvent} event The originating click event
|
||||||
|
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
|
||||||
|
* @returns {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
redirectToRoot: img ? [img] : [],
|
||||||
|
callback: (path) => {
|
||||||
|
this.document.update({ [attr]: path })
|
||||||
|
},
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10,
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit an existing item within the Actor
|
||||||
|
* Start with the uuid, if it's not found, fallback to the id (as Embedded item in the actor)
|
||||||
|
* @this PrismRPGCharacterSheet
|
||||||
|
* @param {PointerEvent} event The originating click event
|
||||||
|
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||||
|
*/
|
||||||
|
static async #onItemEdit(event, target) {
|
||||||
|
const id = target.getAttribute("data-item-id")
|
||||||
|
const uuid = target.getAttribute("data-item-uuid")
|
||||||
|
let item
|
||||||
|
item = await fromUuid(uuid)
|
||||||
|
if (!item) item = this.document.items.get(id)
|
||||||
|
if (!item) return
|
||||||
|
item.sheet.render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing talent within the Actor
|
||||||
|
* Use the uuid to display the talent sheet
|
||||||
|
* @param {PointerEvent} event The originating click event
|
||||||
|
* @param {HTMLElement} target the capturing HTML element which defined a [data-action]
|
||||||
|
*/
|
||||||
|
static async #onItemDelete(event, target) {
|
||||||
|
const itemUuid = target.getAttribute("data-item-uuid")
|
||||||
|
const talent = await fromUuid(itemUuid)
|
||||||
|
await talent.deleteDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the creation of a new attack item.
|
||||||
|
*
|
||||||
|
* @param {Event} event The event that triggered the creation of the attack.
|
||||||
|
* @param {Object} target The target object where the attack will be created.
|
||||||
|
* @private
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static #onCreateSpell(event, target) {
|
||||||
|
const item = this.document.createEmbeddedDocuments("Item", [{ name: "Nouveau sortilège", type: "spell" }])
|
||||||
|
}
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
export default class PrismRPGItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||||
|
/**
|
||||||
|
* Different sheet modes.
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["prismrpg", "item"],
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
height: "auto",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
submitOnChange: true,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true,
|
||||||
|
},
|
||||||
|
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
|
||||||
|
actions: {
|
||||||
|
toggleSheet: PrismRPGItemSheet.#onToggleSheet,
|
||||||
|
editImage: PrismRPGItemSheet.#onEditImage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current sheet mode.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the sheet currently in 'Play' mode?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isPlayMode() {
|
||||||
|
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the sheet currently in 'Edit' mode?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isEditMode() {
|
||||||
|
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
let context = await super._prepareContext()
|
||||||
|
context.fields = this.document.schema.fields
|
||||||
|
context.systemFields = this.document.system.schema.fields
|
||||||
|
context.item = this.document
|
||||||
|
context.system = this.document.system
|
||||||
|
context.source = this.document.toObject()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
context.isEditMode = this.isEditMode
|
||||||
|
context.isPlayMode = this.isPlayMode
|
||||||
|
context.isEditable = this.isEditable
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_onRender(context, options) {
|
||||||
|
super._onRender(context, options)
|
||||||
|
this.#dragDrop.forEach((d) => d.bind(this.element))
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region Drag-and-Drop Workflow
|
||||||
|
/**
|
||||||
|
* Create drag-and-drop workflow handlers for this Application
|
||||||
|
* @returns {DragDrop[]} An array of DragDrop handlers
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
return this.options.dragDrop.map((d) => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this),
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragstart: this._onDragStart.bind(this),
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this),
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define whether a user is able to begin a dragstart workflow for a given drag selector
|
||||||
|
* @param {string} selector The candidate HTML selector for dragging
|
||||||
|
* @returns {boolean} Can the current user drag this selector?
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_canDragStart(selector) {
|
||||||
|
return this.isEditable
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
|
||||||
|
* @param {string} selector The candidate HTML selector for the drop target
|
||||||
|
* @returns {boolean} Can the current user drop on this selector?
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_canDragDrop(selector) {
|
||||||
|
return this.isEditable && this.document.isOwner
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur at the beginning of a drag start workflow.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_onDragStart(event) {
|
||||||
|
const el = event.currentTarget
|
||||||
|
if ("link" in event.target.dataset) return
|
||||||
|
|
||||||
|
// Extract the data you need
|
||||||
|
let dragData = null
|
||||||
|
|
||||||
|
if (!dragData) return
|
||||||
|
|
||||||
|
// Set data transfer
|
||||||
|
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is over a drop target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
_onDragOver(event) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is dropped on a target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _onDrop(event) { }
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// #region Actions
|
||||||
|
/**
|
||||||
|
* Handle toggling between Edit and Play mode.
|
||||||
|
* @param {Event} event The initiating click event.
|
||||||
|
* @param {HTMLElement} target The current target of the event listener.
|
||||||
|
*/
|
||||||
|
static #onToggleSheet(event, target) {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle changing a Document's image.
|
||||||
|
*
|
||||||
|
* @this PrismRPGCharacterSheet
|
||||||
|
* @param {PointerEvent} event The originating click event
|
||||||
|
* @param {HTMLElement} target The capturing HTML element which defined a [data-action]
|
||||||
|
* @returns {Promise}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
redirectToRoot: img ? [img] : [],
|
||||||
|
callback: (path) => {
|
||||||
|
this.document.update({ [attr]: path })
|
||||||
|
},
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10,
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import PrismRPGActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
import PrismRPGRoll from "../../documents/roll.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGCharacterSheet extends PrismRPGActorSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["character"],
|
||||||
|
position: {
|
||||||
|
width: 972,
|
||||||
|
height: 780,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["character-content"],
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
createEquipment: PrismRPGCharacterSheet.#onCreateEquipment,
|
||||||
|
rangedAttackDefense: PrismRPGCharacterSheet.#onRangedAttackDefense,
|
||||||
|
rollInitiative: PrismRPGCharacterSheet.#onRollInitiative,
|
||||||
|
armorHitPointsPlus: PrismRPGCharacterSheet.#onArmorHitPointsPlus,
|
||||||
|
armorHitPointsMinus: PrismRPGCharacterSheet.#onArmorHitPointsMinus,
|
||||||
|
divinityPointsPlus: PrismRPGCharacterSheet.#onDivinityPointsPlus,
|
||||||
|
divinityPointsMinus: PrismRPGCharacterSheet.#onDivinityPointsMinus,
|
||||||
|
aetherPointsPlus: PrismRPGCharacterSheet.#onAetherPointsPlus,
|
||||||
|
aetherPointsMinus: PrismRPGCharacterSheet.#onAetherPointsMinus,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-main.hbs",
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
template: "templates/generic/tab-navigation.hbs",
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-skills.hbs",
|
||||||
|
},
|
||||||
|
combat: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-combat.hbs",
|
||||||
|
},
|
||||||
|
equipment: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-equipment.hbs",
|
||||||
|
},
|
||||||
|
spells: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-spells.hbs",
|
||||||
|
},
|
||||||
|
miracles: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-miracles.hbs",
|
||||||
|
},
|
||||||
|
biography: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/character-biography.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
tabGroups = {
|
||||||
|
sheet: "skills",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare an array of form header tabs.
|
||||||
|
* @returns {Record<string, Partial<ApplicationTab>>}
|
||||||
|
*/
|
||||||
|
#getTabs() {
|
||||||
|
let tabs = {
|
||||||
|
skills: { id: "skills", group: "sheet", icon: "fa-solid fa-shapes", label: "PRISMRPG.Label.skills" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "PRISMRPG.Label.combat" },
|
||||||
|
equipment: { id: "equipment", group: "sheet", icon: "fa-solid fa-backpack", label: "PRISMRPG.Label.equipment" },
|
||||||
|
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "PRISMRPG.Label.biography" },
|
||||||
|
}
|
||||||
|
if (this.actor.system.biodata.magicUser) {
|
||||||
|
tabs.spells = { id: "spells", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-wand-magic-sparkles", label: "PRISMRPG.Label.spells" }
|
||||||
|
}
|
||||||
|
if (this.actor.system.biodata.clericUser) {
|
||||||
|
tabs.miracles = { id: "miracles", group: "sheet", icon: "fa-sharp-duotone fa-solid fa-hands-praying", label: "PRISMRPG.Label.miracles" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main":
|
||||||
|
break
|
||||||
|
case "skills":
|
||||||
|
context.tab = context.tabs.skills
|
||||||
|
context.skills = doc.itemTypes.skill
|
||||||
|
context.gifts = doc.itemTypes.gift
|
||||||
|
context.vulnerabilities = doc.itemTypes.vulnerability
|
||||||
|
break
|
||||||
|
case "spells":
|
||||||
|
context.tab = context.tabs.spells
|
||||||
|
context.spells = doc.itemTypes.spell
|
||||||
|
context.hasSpells = context.spells.length > 0
|
||||||
|
break
|
||||||
|
case "miracles":
|
||||||
|
context.tab = context.tabs.miracles
|
||||||
|
context.miracles = doc.itemTypes.miracle
|
||||||
|
context.hasMiracles = context.miracles.length > 0
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
context.armors = doc.itemTypes.armor
|
||||||
|
context.shields = doc.itemTypes.shield
|
||||||
|
break
|
||||||
|
case "equipment":
|
||||||
|
context.tab = context.tabs.equipment
|
||||||
|
context.equipments = doc.itemTypes.equipment
|
||||||
|
break
|
||||||
|
case "biography":
|
||||||
|
context.tab = context.tabs.biography
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
|
||||||
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region Drag-and-Drop Workflow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is dropped on a target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _onDrop(event) {
|
||||||
|
if (!this.isEditable || !this.isEditMode) return
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event)
|
||||||
|
|
||||||
|
// Handle different data types
|
||||||
|
if (data.type === "Item") {
|
||||||
|
const item = await fromUuid(data.uuid)
|
||||||
|
return this._onDropItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRangedAttackDefense(event, target) {
|
||||||
|
// Future use : const hasTarget = false
|
||||||
|
|
||||||
|
let roll = await PrismRPGRoll.promptRangedDefense({
|
||||||
|
actorId: this.actor.id,
|
||||||
|
actorName: this.actor.name,
|
||||||
|
actorImage: this.actor.img,
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollInitiative(event, target) {
|
||||||
|
await this.document.system.rollInitiative()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onArmorHitPointsPlus(event, target) {
|
||||||
|
let armorHP = this.actor.system.combat.armorHitPoints
|
||||||
|
armorHP += 1
|
||||||
|
this.actor.update({ "system.combat.armorHitPoints": armorHP })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onArmorHitPointsMinus(event, target) {
|
||||||
|
let armorHP = this.actor.system.combat.armorHitPoints
|
||||||
|
armorHP -= 1
|
||||||
|
this.actor.update({ "system.combat.armorHitPoints": Math.max(armorHP, 0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onDivinityPointsPlus(event, target) {
|
||||||
|
let points = this.actor.system.divinityPoints.value
|
||||||
|
points += 1
|
||||||
|
points = Math.min(points, this.actor.system.divinityPoints.max)
|
||||||
|
this.actor.update({ "system.divinityPoints.value": points })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onDivinityPointsMinus(event, target) {
|
||||||
|
let points = this.actor.system.divinityPoints.value
|
||||||
|
points -= 1
|
||||||
|
points = Math.max(points, 0)
|
||||||
|
this.actor.update({ "system.divinityPoints.value": points })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onAetherPointsPlus(event, target) {
|
||||||
|
let points = this.actor.system.aetherPoints.value
|
||||||
|
points += 1
|
||||||
|
points = Math.min(points, this.actor.system.aetherPoints.max)
|
||||||
|
this.actor.update({ "system.aetherPoints.value": points })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onAetherPointsMinus(event, target) {
|
||||||
|
let points = this.actor.system.aetherPoints.value
|
||||||
|
points -= 1
|
||||||
|
points = Math.max(points, 0)
|
||||||
|
this.actor.update({ "system.aetherPoints.value": points })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onCreateEquipment(event, target) {
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
// Inputs with class `item-quantity`
|
||||||
|
const woundDescription = this.element.querySelectorAll('.wound-data')
|
||||||
|
for (const input of woundDescription) {
|
||||||
|
input.addEventListener("change", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
const newValue = e.currentTarget.value
|
||||||
|
const index = e.currentTarget.dataset.index
|
||||||
|
const fieldName = e.currentTarget.dataset.name
|
||||||
|
let tab = foundry.utils.duplicate(this.actor.system.hp.wounds)
|
||||||
|
tab[index][fieldName] = newValue
|
||||||
|
console.log(tab, index, fieldName, newValue)
|
||||||
|
this.actor.update({ "system.hp.wounds": tab });
|
||||||
|
})
|
||||||
|
}
|
||||||
|
super._onRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the roll action triggered by user interaction.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} event The event object representing the user interaction.
|
||||||
|
* @param {HTMLElement} target The target element that triggered the roll.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the roll action is complete.
|
||||||
|
*
|
||||||
|
* @throws {Error} Throws an error if the roll type is not recognized.
|
||||||
|
*
|
||||||
|
* @description This method checks the current mode (edit or not) and determines the type of roll
|
||||||
|
* (save, resource, or damage) based on the target element's data attributes. It retrieves the
|
||||||
|
* corresponding value from the document's system and performs the roll.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async _onRoll(event, target) {
|
||||||
|
if (this.isEditMode) return
|
||||||
|
const rollType = event.target.dataset.rollType
|
||||||
|
let rollKey = event.target.dataset.rollKey;
|
||||||
|
let rollDice = event.target.dataset?.rollDice;
|
||||||
|
|
||||||
|
this.actor.prepareRoll(rollType, rollKey, rollDice)
|
||||||
|
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGEquipmentSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["equipment"],
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["equipment-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/equipment.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGGiftSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["gift"],
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["gift-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/gift.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGMiracleSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["miracle"],
|
||||||
|
position: {
|
||||||
|
width: 450,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["miracle-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/miracle.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import PrismRPGActorSheet from "./base-actor-sheet.mjs"
|
||||||
|
import PrismRPGRoll from "../../documents/roll.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGMonsterSheet extends PrismRPGActorSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["monster"],
|
||||||
|
position: {
|
||||||
|
width: 1060,
|
||||||
|
height: 780,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["monster-content"],
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
rangedAttackDefense: PrismRPGMonsterSheet.#onRangedAttackDefense,
|
||||||
|
rollInitiative: PrismRPGMonsterSheet.#onRollInitiative,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/monster-main.hbs",
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
template: "templates/generic/tab-navigation.hbs",
|
||||||
|
},
|
||||||
|
combat: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/monster-combat.hbs",
|
||||||
|
},
|
||||||
|
biography: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/monster-biography.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
tabGroups = {
|
||||||
|
sheet: "combat",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare an array of form header tabs.
|
||||||
|
* @returns {Record<string, Partial<ApplicationTab>>}
|
||||||
|
*/
|
||||||
|
#getTabs() {
|
||||||
|
let tabs = {
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-swords", label: "PRISMRPG.Label.combat" },
|
||||||
|
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "PRISMRPG.Label.biography" },
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main":
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
break
|
||||||
|
case "biography":
|
||||||
|
context.tab = context.tabs.biography
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.description, { async: true })
|
||||||
|
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(doc.system.notes, { async: true })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// #region Drag-and-Drop Workflow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback actions which occur when a dragged element is dropped on a target.
|
||||||
|
* @param {DragEvent} event The originating DragEvent
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _onDrop(event) {
|
||||||
|
if (!this.isEditable || !this.isEditMode) return
|
||||||
|
const data = TextEditor.getDragEventData(event)
|
||||||
|
|
||||||
|
// Handle different data types
|
||||||
|
switch (data.type) {
|
||||||
|
case "Item":
|
||||||
|
const item = await fromUuid(data.uuid)
|
||||||
|
return this._onDropItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRangedAttackDefense(event, target) {
|
||||||
|
const hasTarget = false
|
||||||
|
|
||||||
|
let roll = await PrismRPGRoll.promptRangedDefense({
|
||||||
|
actorId: this.actor.id,
|
||||||
|
actorName: this.actor.name,
|
||||||
|
actorImage: this.actor.img,
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollInitiative(event, target) {
|
||||||
|
await this.document.system.rollInitiative(event, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
getBestWeaponClassSkill(skills, rollType, multiplier = 1.0) {
|
||||||
|
let maxValue = 0
|
||||||
|
let goodSkill = skills[0]
|
||||||
|
for (let s of skills) {
|
||||||
|
if (rollType === "weapon-attack") {
|
||||||
|
if (s.system.weaponBonus.attack > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.attack)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rollType === "weapon-defense") {
|
||||||
|
if (s.system.weaponBonus.defense > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.defense)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rollType.includes("weapon-damage")) {
|
||||||
|
if (s.system.weaponBonus.damage > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.damage)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goodSkill.weaponSkillModifier = maxValue * multiplier
|
||||||
|
return goodSkill
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the roll action triggered by user interaction.
|
||||||
|
*
|
||||||
|
* @param {PointerEvent} event The event object representing the user interaction.
|
||||||
|
* @param {HTMLElement} target The target element that triggered the roll.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the roll action is complete.
|
||||||
|
*
|
||||||
|
* @throws {Error} Throws an error if the roll type is not recognized.
|
||||||
|
*
|
||||||
|
* @description This method checks the current mode (edit or not) and determines the type of roll
|
||||||
|
* (save, resource, or damage) based on the target element's data attributes. It retrieves the
|
||||||
|
* corresponding value from the document's system and performs the roll.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async _onRoll(event, target) {
|
||||||
|
if (this.isEditMode) return
|
||||||
|
const rollType = event.target.dataset.rollType
|
||||||
|
let rollKey = event.target.dataset.rollKey
|
||||||
|
let rollDice = event.target.dataset?.rollDice || "0"
|
||||||
|
this.actor.system.prepareMonsterRoll(rollType, rollKey, rollDice)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGShieldSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["shield"],
|
||||||
|
position: {
|
||||||
|
width: 620,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["shield-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/shield.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGSkillSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["skill"],
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["skill-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/skill.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGSpellSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["spell"],
|
||||||
|
position: {
|
||||||
|
width: 450,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["spell-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/spell.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGVulnerabilitySheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["vulnerability"],
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["vulnerability-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/vulnerability.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import PrismRPGItemSheet from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGWeaponSheet extends PrismRPGItemSheet {
|
||||||
|
/** @override */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["weapon"],
|
||||||
|
position: {
|
||||||
|
width: 620,
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
contentClasses: ["weapon-content"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
main: {
|
||||||
|
template: "systems/fvtt-prism-rpg/templates/weapon.hbs",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description, { async: true })
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const TYPE = Object.freeze({
|
||||||
|
light: { id: "light", label: "PRISMRPG.Armor.Category.light" },
|
||||||
|
medium: { id: "medium", label: "PRISMRPG.Armor.Category.medium" },
|
||||||
|
heavy: { id: "medium", label: "PRISMRPG.Armor.Category.heavy" }
|
||||||
|
})
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
export const CHARACTERISTICS = Object.freeze({
|
||||||
|
str: {
|
||||||
|
id: "str",
|
||||||
|
label: "PRISMRPG.Label.str"
|
||||||
|
},
|
||||||
|
int: {
|
||||||
|
id: "int",
|
||||||
|
label: "PRISMRPG.Character.int.label"
|
||||||
|
},
|
||||||
|
wis: {
|
||||||
|
id: "wis",
|
||||||
|
label: "PRISMRPG.Character.wis.label"
|
||||||
|
},
|
||||||
|
dex: {
|
||||||
|
id: "dex",
|
||||||
|
label: "PRISMRPG.Character.dex.label"
|
||||||
|
},
|
||||||
|
con: {
|
||||||
|
id: "con",
|
||||||
|
label: "PRISMRPG.Character.con.label"
|
||||||
|
},
|
||||||
|
cha: {
|
||||||
|
id: "cha",
|
||||||
|
label: "PRISMRPG.Character.cha.label"
|
||||||
|
},
|
||||||
|
luc: {
|
||||||
|
id: "luc",
|
||||||
|
label: "PRISMRPG.Character.luc.label"
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
id: "app",
|
||||||
|
label: "PRISMRPG.Character.app.label"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const CHALLENGES = Object.freeze({
|
||||||
|
str: {
|
||||||
|
id: "str",
|
||||||
|
label: "PRISMRPG.Character.str.label"
|
||||||
|
},
|
||||||
|
agility: {
|
||||||
|
id: "agility",
|
||||||
|
label: "PRISMRPG.Character.agility.label"
|
||||||
|
},
|
||||||
|
dying: {
|
||||||
|
id: "dying",
|
||||||
|
label: "PRISMRPG.Character.dying.label"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const SAVES = Object.freeze({
|
||||||
|
will: {
|
||||||
|
id: "will",
|
||||||
|
label: "PRISMRPG.Character.will.label"
|
||||||
|
},
|
||||||
|
dodge: {
|
||||||
|
id: "dodge",
|
||||||
|
label: "PRISMRPG.Character.dodge.label"
|
||||||
|
},
|
||||||
|
toughness: {
|
||||||
|
id: "toughness",
|
||||||
|
label: "PRISMRPG.Character.toughness.label"
|
||||||
|
},
|
||||||
|
contagion: {
|
||||||
|
id: "contagion",
|
||||||
|
label: "PRISMRPG.Character.contagion.label"
|
||||||
|
},
|
||||||
|
poison: {
|
||||||
|
id: "poison",
|
||||||
|
label: "PRISMRPG.Character.poison.label"
|
||||||
|
},
|
||||||
|
pain: {
|
||||||
|
id: "pain",
|
||||||
|
label: "PRISMRPG.Character.pain.label"
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
export const CATEGORY = Object.freeze({
|
||||||
|
essentialkit: {
|
||||||
|
id: "essentialkit",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.EssentialKit",
|
||||||
|
},
|
||||||
|
classkit: {
|
||||||
|
id: "classkit",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.ClassKit",
|
||||||
|
},
|
||||||
|
clothing: {
|
||||||
|
id: "clothing",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Clothing",
|
||||||
|
},
|
||||||
|
music: {
|
||||||
|
id: "music",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Music",
|
||||||
|
},
|
||||||
|
sleeping: {
|
||||||
|
id: "sleeping",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Sleeping",
|
||||||
|
},
|
||||||
|
landtransport: {
|
||||||
|
id: "landtransport",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.LandTransport",
|
||||||
|
},
|
||||||
|
watertransport: {
|
||||||
|
id: "watertransport",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.WaterTransport",
|
||||||
|
},
|
||||||
|
mount: {
|
||||||
|
id: "mount",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Mount",
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
id: "light",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Light",
|
||||||
|
},
|
||||||
|
loadbearing: {
|
||||||
|
id: "loadbearing",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.LoadBearing",
|
||||||
|
},
|
||||||
|
misc: {
|
||||||
|
id: "misc",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.Misc",
|
||||||
|
},
|
||||||
|
fooddrink: {
|
||||||
|
id: "fooddrink",
|
||||||
|
label: "PRISMRPG.EquipmentCategories.FoodDrink",
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
export const MONSTER_CHARACTERISTICS = Object.freeze({
|
||||||
|
int: {
|
||||||
|
id: "int",
|
||||||
|
label: "PRISMRPG.Character.int.label"
|
||||||
|
},
|
||||||
|
dex: {
|
||||||
|
id: "dex",
|
||||||
|
label: "PRISMRPG.Character.dex.label"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const MONSTER_RESIST = Object.freeze({
|
||||||
|
resistTorture: {
|
||||||
|
id: "resistTorture",
|
||||||
|
label: "PRISMRPG.Monster.resistTorture.label"
|
||||||
|
},
|
||||||
|
resistPerformance: {
|
||||||
|
id: "resistPerformance",
|
||||||
|
label: "PRISMRPG.Monster.resistPerformance.label"
|
||||||
|
},
|
||||||
|
resistIntimidation: {
|
||||||
|
id: "resistIntimidation",
|
||||||
|
label: "PRISMRPG.Monster.resistIntimidation.label"
|
||||||
|
},
|
||||||
|
perception: {
|
||||||
|
id: "perception",
|
||||||
|
label: "PRISMRPG.Monster.perception.label"
|
||||||
|
},
|
||||||
|
stealth: {
|
||||||
|
id: "stealth",
|
||||||
|
label: "PRISMRPG.Monster.stealth.label"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const MONSTER_SAVES = Object.freeze({
|
||||||
|
will: {
|
||||||
|
id: "will",
|
||||||
|
label: "PRISMRPG.Character.will.label"
|
||||||
|
},
|
||||||
|
dodge: {
|
||||||
|
id: "dodge",
|
||||||
|
label: "PRISMRPG.Character.dodge.label"
|
||||||
|
},
|
||||||
|
toughness: {
|
||||||
|
id: "toughness",
|
||||||
|
label: "PRISMRPG.Character.toughness.label"
|
||||||
|
},
|
||||||
|
contagion: {
|
||||||
|
id: "contagion",
|
||||||
|
label: "PRISMRPG.Character.contagion.label"
|
||||||
|
},
|
||||||
|
poison: {
|
||||||
|
id: "poison",
|
||||||
|
label: "PRISMRPG.Character.poison.label"
|
||||||
|
},
|
||||||
|
paincourage: {
|
||||||
|
id: "paincourage",
|
||||||
|
label: "PRISMRPG.Character.painCourage.label"
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const CATEGORY = Object.freeze({
|
||||||
|
layperson: {
|
||||||
|
id: "layperson",
|
||||||
|
label: "PRISMRPG.Skill.Category.layperson",
|
||||||
|
},
|
||||||
|
professional: {
|
||||||
|
id: "professional",
|
||||||
|
label: "PRISMRPG.Skill.Category.professional",
|
||||||
|
},
|
||||||
|
weapon: {
|
||||||
|
id: "weapon",
|
||||||
|
label: "PRISMRPG.Skill.Category.weapon",
|
||||||
|
},
|
||||||
|
armor: {
|
||||||
|
id: "armor",
|
||||||
|
label: "PRISMRPG.Skill.Category.armor",
|
||||||
|
},
|
||||||
|
resist: {
|
||||||
|
id: "resist",
|
||||||
|
label: "PRISMRPG.Skill.Category.resist",
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export const RANGE = Object.freeze({
|
||||||
|
na: {
|
||||||
|
id: "na",
|
||||||
|
label: "PRISMRPG.Spell.Range.na",
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
id: "contact",
|
||||||
|
label: "PRISMRPG.Spell.Range.contact",
|
||||||
|
},
|
||||||
|
proche: {
|
||||||
|
id: "proche",
|
||||||
|
label: "PRISMRPG.Spell.Range.proche",
|
||||||
|
},
|
||||||
|
loin: {
|
||||||
|
id: "loin",
|
||||||
|
label: "PRISMRPG.Spell.Range.loin",
|
||||||
|
},
|
||||||
|
distant: {
|
||||||
|
id: "distant",
|
||||||
|
label: "PRISMRPG.Spell.Range.distant",
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
import * as CHARACTER from "./character.mjs"
|
||||||
|
import * as WEAPON from "./weapon.mjs"
|
||||||
|
import * as ARMOR from "./armor.mjs"
|
||||||
|
import * as SPELL from "./spell.mjs"
|
||||||
|
import * as SKILL from "./skill.mjs"
|
||||||
|
import * as EQUIPMENT from "./equipment.mjs"
|
||||||
|
import * as CHARACTERISTICS from "./characteristic-tables.mjs"
|
||||||
|
import * as MONSTER from "./monster.mjs"
|
||||||
|
|
||||||
|
export const SYSTEM_ID = "fvtt-prism-rpg"
|
||||||
|
export const DEV_MODE = false
|
||||||
|
|
||||||
|
export const MONEY = {
|
||||||
|
tinbit: {
|
||||||
|
id: "tinbit",
|
||||||
|
abbrev: "tb",
|
||||||
|
label: "PRISMRPG.Money.Tinbits",
|
||||||
|
valuetb: 1
|
||||||
|
},
|
||||||
|
copper: {
|
||||||
|
id: "copper",
|
||||||
|
abbrev: "cp",
|
||||||
|
label: "PRISMRPG.Money.Coppers",
|
||||||
|
valuetb: 10
|
||||||
|
},
|
||||||
|
silver: {
|
||||||
|
id: "silver",
|
||||||
|
abbrev: "sp",
|
||||||
|
label: "PRISMRPG.Money.Silvers",
|
||||||
|
valuetb: 100
|
||||||
|
},
|
||||||
|
gold: {
|
||||||
|
id: "gold",
|
||||||
|
abbrev: "gp",
|
||||||
|
label: "PRISMRPG.Money.Golds",
|
||||||
|
valuetb: 1000
|
||||||
|
},
|
||||||
|
platinum: {
|
||||||
|
id: "platinum",
|
||||||
|
abbrev: "pp",
|
||||||
|
label: "PRISMRPG.Money.Platinums",
|
||||||
|
valuetb: 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MORTAL_CHOICES = {
|
||||||
|
"mankind": { label: "Mankind", id: "mankind", defenseBonus: 0 },
|
||||||
|
"elf": { label: "Elf", id: "elf", defenseBonus: 0 },
|
||||||
|
"dwarf": { label: "Dwarf", id: "dwarf", defenseBonus: 0 },
|
||||||
|
"halfelf": { label: "Half-Elf", id: "halfelf", defenseBonus: 0 },
|
||||||
|
"halforc": { label: "Half-Orc", id: "halforc", defenseBonus: 0 },
|
||||||
|
"gnome": { label: "Gnome", id: "gnome", defenseBonus: 2 },
|
||||||
|
"halflings": { label: "Halfling", id: "halflings", defenseBonus: 2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FAVOR_CHOICES = {
|
||||||
|
"none": { label: "None", value: "none" },
|
||||||
|
"favor": { label: "Favor", value: "favor" },
|
||||||
|
"disfavor": { label: "Disfavor", value: "disfavor" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MOVEMENT_CHOICES = {
|
||||||
|
"none": { label: "None (D20E Disfavor)", disfavor: true, value: "2D20kl" },
|
||||||
|
"walk": { label: "Walk (D20E)", disfavor: true, value: "D20" },
|
||||||
|
"incombat": { label: "In Combat (D20E)", favor: false, value: "D20" },
|
||||||
|
"run": { label: "Jog/Run/Sprint (D20E Favor)", favor: true, value: "2D20kh" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MOVE_DIRECTION_CHOICES = {
|
||||||
|
"away": { label: "Away (+0)", value: "+0" },
|
||||||
|
"toward": { label: "Toward (0)", value: "0" },
|
||||||
|
"lateral": { label: "Lateral (Red +5)", value: "+5" },
|
||||||
|
"none": { label: "None (+0)", value: "0" },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SIZE_CHOICES = {
|
||||||
|
"tiny": { label: "Tiny (Blue +11)", value: "+11" },
|
||||||
|
"small": { label: "Small (Purple +7)", value: "+7" },
|
||||||
|
"medium": { label: "Medium (Red +5)", value: "+5" },
|
||||||
|
"large": { label: "Large (Yellow +1)", value: "+1" },
|
||||||
|
"huge": { label: "Huge (0)", value: "0" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RANGE_CHOICES = {
|
||||||
|
"pointblank": { label: "Point Blank (Special)", value: "pointblank" },
|
||||||
|
"short": { label: "Short (+0)", value: "0" },
|
||||||
|
"medium": { label: "Medium (Red +5)", value: "+5" },
|
||||||
|
"long": { label: "Long (Purle +7)", value: "+7" },
|
||||||
|
"extreme": { label: "Extreme (Grey +9)", value: "+9" },
|
||||||
|
"beyondskill": { label: "Beyond Skill (Blue +11)", value: "beyondskill" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ATTACKER_AIM_CHOICES = {
|
||||||
|
"simple": { label: "Simple (+0)", value: "0" },
|
||||||
|
"careful": { label: "Careful (Red +5)", value: "+4" },
|
||||||
|
"focused": { label: "Focused (Grey +9)", value: "+9" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SPELL_LETHARGY_DICE = [
|
||||||
|
{ dice: "D6", level: "1-5", value: "6", maxLevel: 5 },
|
||||||
|
{ dice: "D8", level: "6-10", value: "8", maxLevel: 10 },
|
||||||
|
{ dice: "D10", value: "10", level: "11-15", maxLevel: 15 },
|
||||||
|
{ dice: "D12", value: "12", level: "16-20", maxLevel: 20 },
|
||||||
|
{ dice: "D20", value: "20", level: "21-25", maxLevel: 25 }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const GRANTED_DICE_CHOICES = {
|
||||||
|
"0": { label: "None", value: "0" },
|
||||||
|
"D2": { label: "D2", value: "D2" },
|
||||||
|
"D3": { label: "D3", value: "D3" },
|
||||||
|
"D4": { label: "D4", value: "D4" },
|
||||||
|
"D6": { label: "D6", value: "D6" },
|
||||||
|
"D8": { label: "D8", value: "D8" },
|
||||||
|
"D10": { label: "D10", value: "D10" },
|
||||||
|
"D12": { label: "D12", value: "D12" },
|
||||||
|
"D20": { label: "D20", value: "D20" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIATIVE_DICE_CHOICES_PER_CLASS = {
|
||||||
|
"untrained": [
|
||||||
|
{ "name": "Asleep or totally distracted (2D12)", "value": "2D12" },
|
||||||
|
{ "name": "Awake but unsuspecting (2D8)", "value": "2D8" },
|
||||||
|
{ "name": "Declared Ready on Alert (2D6)", "value": "2D6" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (2D4)", "value": "2D4" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (2D3)", "value": "2D3" }*/
|
||||||
|
],
|
||||||
|
"fighter": [
|
||||||
|
{ "name": "Asleep or totally distracted (1D12)", "value": "1D12" },
|
||||||
|
{ "name": "Awake but unsuspecting (1D8)", "value": "1D8" },
|
||||||
|
{ "name": "Declared Ready on Alert (1)", "value": "1" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (1D4)", "value": "1D4" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (1D3)", "value": "1D3" }*/
|
||||||
|
],
|
||||||
|
"rogue": [
|
||||||
|
{ "name": "Asleep or totally distracted (1D10)", "value": "1D10" },
|
||||||
|
{ "name": "Awake but unsuspecting (1D8)", "value": "1D8" },
|
||||||
|
{ "name": "Declared Ready on Alert (1)", "value": "1" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (1D3)", "value": "1D3" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (1D2)", "value": "1D2" }*/
|
||||||
|
],
|
||||||
|
"ranger": [
|
||||||
|
{ "name": "Asleep or totally distracted (1D10)", "value": "1D10" },
|
||||||
|
{ "name": "Awake but unsuspecting (1D8)", "value": "1D8" },
|
||||||
|
{ "name": "Declared Ready on Alert (1)", "value": "1" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (1D4)", "value": "1D4" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (1D3)", "value": "1D3" }*/
|
||||||
|
],
|
||||||
|
"cleric": [
|
||||||
|
{ "name": "Asleep or totally distracted (1D12)", "value": "1D12" },
|
||||||
|
{ "name": "Awake but unsuspecting (1D10)", "value": "1D10" },
|
||||||
|
{ "name": "Declared Ready on Alert (1)", "value": "1" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (1D6)", "value": "1D6" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (1D4)", "value": "1D4" }*/
|
||||||
|
],
|
||||||
|
"magicuser": [
|
||||||
|
{ "name": "Sleeping to recover Aether Points (2D20)", "value": "2D20" },
|
||||||
|
{ "name": "Asleep or totally distracted (1D20)", "value": "1D20" },
|
||||||
|
{ "name": "Awake but unsuspecting (1D12)", "value": "1D12" },
|
||||||
|
{ "name": "Declared Ready on Alert (1)", "value": "1" },
|
||||||
|
/*{ "name": "Aware of the enemy, can hear them but not see (1D8)", "value": "1D8" },
|
||||||
|
{ "name": "Aware and know exactly where the enemy is (1D6)", "value": "1D6" }*/
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHAR_CLASSES = {
|
||||||
|
"untrained": "Untrained",
|
||||||
|
"fighter": "Fighter",
|
||||||
|
"rogue": "Rogue",
|
||||||
|
"ranger": "Ranger",
|
||||||
|
"cleric": "Cleric",
|
||||||
|
"magicuser": "Magic User"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHAR_CLASSES_DEFINES = {
|
||||||
|
"untrained": { id: "untrained", label: "Untrained" },
|
||||||
|
"fighter": { id: "fighter", label: "Fighter" },
|
||||||
|
"rogue": { id: "rogue", label: "Rogue" },
|
||||||
|
"ranger": { id: "ranger", label: "Ranger" },
|
||||||
|
"cleric": { id: "cleric", label: "Cleric" },
|
||||||
|
"magicuser": { id: "magicuser", label: "Magic User" }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DICE_VALUES = {
|
||||||
|
"d3": "D3",
|
||||||
|
"d4": "D4",
|
||||||
|
"d6": "D6",
|
||||||
|
"d8": "D8",
|
||||||
|
"d10": "D10",
|
||||||
|
"d12": "D12",
|
||||||
|
"d20": "D20"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHARACTERISTIC_ATTACK = ["str", "int", "wis", "dex"]
|
||||||
|
export const CHARACTERISTIC_RANGED_ATTACK = ["int", "wis", "dex"]
|
||||||
|
export const CHARACTERISTIC_DEFENSE = ["int", "wis", "dex"]
|
||||||
|
export const CHARACTERISTIC_DAMAGE = ["str"]
|
||||||
|
|
||||||
|
export const DEFENSE_DICE_VALUES = {
|
||||||
|
"0": "0",
|
||||||
|
"d3": "D3",
|
||||||
|
"d4": "D4",
|
||||||
|
"d6": "D6",
|
||||||
|
"d8": "D8",
|
||||||
|
"d10": "D10"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHOICE_DICE = {
|
||||||
|
"D4": "D4",
|
||||||
|
"D6": "D6",
|
||||||
|
"D8": "D8",
|
||||||
|
"D10": "D10",
|
||||||
|
"D12": "D12",
|
||||||
|
"D20": "D20"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIRACLE_TYPES = {
|
||||||
|
"combat": "Combat",
|
||||||
|
"noncombat": "Non-Combat",
|
||||||
|
"ritualfaith": "Ritual of Faith"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SPELL_CRITICAL = {
|
||||||
|
"none": "None",
|
||||||
|
"electric": "Electric",
|
||||||
|
"fire": "Fire",
|
||||||
|
"cold": "Cold",
|
||||||
|
"force": "Force",
|
||||||
|
"acid": "Acid"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHOICE_MODIFIERS = {
|
||||||
|
"-9": "-9",
|
||||||
|
"-8": "-8",
|
||||||
|
"-7": "-7",
|
||||||
|
"-6": "-6",
|
||||||
|
"-5": "-5",
|
||||||
|
"-4": "-4",
|
||||||
|
"-3": "-3",
|
||||||
|
"-2": "-2",
|
||||||
|
"-1": "-1",
|
||||||
|
"+0": "0",
|
||||||
|
"+1": "+1",
|
||||||
|
"+2": "+2",
|
||||||
|
"+3": "+3",
|
||||||
|
"+4": "+4",
|
||||||
|
"+5": "+5",
|
||||||
|
"+6": "+6",
|
||||||
|
"+7": "+7",
|
||||||
|
"+8": "+8",
|
||||||
|
"+9": "+9",
|
||||||
|
"+10": "+10",
|
||||||
|
"+11": "+11",
|
||||||
|
"+12": "+12",
|
||||||
|
"+13": "+13",
|
||||||
|
"+14": "+14",
|
||||||
|
"+15": "+15",
|
||||||
|
"+16": "+16",
|
||||||
|
"+17": "+17",
|
||||||
|
"+18": "+18",
|
||||||
|
"+19": "+19",
|
||||||
|
"+20": "+20",
|
||||||
|
"+21": "+21",
|
||||||
|
"+22": "+22",
|
||||||
|
"+23": "+23",
|
||||||
|
"+24": "+24",
|
||||||
|
"+25": "+25"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ASCII = `
|
||||||
|
······················································································································
|
||||||
|
: :
|
||||||
|
:@@@ @@@@@@@@ @@@@@@@ @@@ @@@ @@@@@@ @@@ @@@@@@@@ @@@@@@ @@@ @@@ @@@@@@@ @@@@@@ @@@@@@ @@@ @@@ :
|
||||||
|
:@@! @@! @!! @@! @@@ @@! @@@ @@! @@! @@! @@@ @@!@!@@@ @!! @@! @@@ !@@ @@! !@@ :
|
||||||
|
:@!! @!!!:! @!! @!@!@!@! @!@!@!@! @!! @!!!:! @!@!@!@! @!@@!!@! @!! @!@!@!@! !@@!! !@!@! :
|
||||||
|
:!!: !!: !!: !!: !!! !!: !!! !!: !!: !!: !!! !!: !!! !!: !!: !!! !:! !!: :
|
||||||
|
:: ::.: : : :: :: : : : : : : : : ::.: : : : : : :: : : : : : ::.: : .: :
|
||||||
|
: :
|
||||||
|
······················································································································
|
||||||
|
`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include all constant definitions within the SYSTEM global export
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const SYSTEM = {
|
||||||
|
id: SYSTEM_ID,
|
||||||
|
CHARACTERISTICS: CHARACTER.CHARACTERISTICS,
|
||||||
|
CHARACTERISTICS_TABLES: CHARACTERISTICS.TABLES,
|
||||||
|
CHARACTERISTICS_MAJOR: CHARACTERISTICS.MAJOR,
|
||||||
|
MONSTER_CHARACTERISTICS: MONSTER.MONSTER_CHARACTERISTICS,
|
||||||
|
MONSTER_RESIST: MONSTER.MONSTER_RESIST,
|
||||||
|
MONSTER_SAVES: MONSTER.MONSTER_SAVES,
|
||||||
|
SAVES: CHARACTER.SAVES,
|
||||||
|
CHALLENGES: CHARACTER.CHALLENGES,
|
||||||
|
SKILL_CATEGORY: SKILL.CATEGORY,
|
||||||
|
ARMOR_TYPE: ARMOR.TYPE,
|
||||||
|
EQUIPMENT_CATEGORY: EQUIPMENT.CATEGORY,
|
||||||
|
SPELL_RANGE: SPELL.RANGE,
|
||||||
|
WEAPON_TYPE: WEAPON.WEAPON_TYPE,
|
||||||
|
WEAPON_CLASS: WEAPON.WEAPON_CLASS,
|
||||||
|
COMBAT_PROGRESSION_DICE: DICE_VALUES,
|
||||||
|
SHIELD_DEFENSE_DICE: DEFENSE_DICE_VALUES,
|
||||||
|
WEAPON_CATEGORIES: WEAPON.WEAPON_CATEGORIES,
|
||||||
|
CHARACTERISTIC_ATTACK,
|
||||||
|
CHARACTERISTIC_RANGED_ATTACK,
|
||||||
|
CHARACTERISTIC_DEFENSE,
|
||||||
|
CHARACTERISTIC_DAMAGE,
|
||||||
|
INITIATIVE_DICE_CHOICES_PER_CLASS,
|
||||||
|
CHAR_CLASSES,
|
||||||
|
CHAR_CLASSES_DEFINES,
|
||||||
|
MONEY,
|
||||||
|
ASCII,
|
||||||
|
CHOICE_MODIFIERS,
|
||||||
|
CHOICE_DICE,
|
||||||
|
DEV_MODE,
|
||||||
|
MOVEMENT_CHOICES,
|
||||||
|
MOVE_DIRECTION_CHOICES,
|
||||||
|
SIZE_CHOICES,
|
||||||
|
RANGE_CHOICES,
|
||||||
|
FAVOR_CHOICES,
|
||||||
|
ATTACKER_AIM_CHOICES,
|
||||||
|
MORTAL_CHOICES,
|
||||||
|
SPELL_CRITICAL,
|
||||||
|
MIRACLE_TYPES,
|
||||||
|
SPELL_LETHARGY_DICE,
|
||||||
|
GRANTED_DICE_CHOICES
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
export const WEAPON_TYPE = {
|
||||||
|
"melee": "PRISMRPG.Weapon.WeaponType.melee",
|
||||||
|
"ranged": "PRISMRPG.Weapon.WeaponType.ranged"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WEAPON_CLASS = {
|
||||||
|
"longblade": "PRISMRPG.Weapon.WeaponClass.longblade",
|
||||||
|
"shortblade": "PRISMRPG.Weapon.WeaponClass.shortblade",
|
||||||
|
"mediumblade": "PRISMRPG.Weapon.WeaponClass.mediumblade",
|
||||||
|
"axe": "PRISMRPG.Weapon.WeaponClass.axe",
|
||||||
|
"hammer": "PRISMRPG.Weapon.WeaponClass.hammer",
|
||||||
|
"mace": "PRISMRPG.Weapon.WeaponClass.mace",
|
||||||
|
"flail": "PRISMRPG.Weapon.WeaponClass.flail",
|
||||||
|
"bow": "PRISMRPG.Weapon.WeaponClass.bow",
|
||||||
|
"sling": "PRISMRPG.Weapon.WeaponClass.sling",
|
||||||
|
"thrown": "PRISMRPG.Weapon.WeaponClass.thrown",
|
||||||
|
"polearm": "PRISMRPG.Weapon.WeaponClass.polearm",
|
||||||
|
"unarmed" : "PRISMRPG.Weapon.WeaponClass.unarmed"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WEAPON_CATEGORIES = {
|
||||||
|
"longblade": ["mediumblade", "shortblade"],
|
||||||
|
"shortblade": ["mediumblade", "longblade"],
|
||||||
|
"mediumblade": ["shortblade", "longblade"],
|
||||||
|
"axe": ["hammer", "mace", "flail", "bow", "sling", "thrown", "polearm"],
|
||||||
|
"hammer": ["axe", "mace", "flail", "bow", "sling", "thrown", "polearm"],
|
||||||
|
"mace": ["axe", "hammer", "flail", "bow", "sling", "thrown", "polearm"],
|
||||||
|
"flail": ["axe", "hammer", "mace", "bow", "sling", "thrown", "polearm"],
|
||||||
|
"bow": ["axe", "hammer", "mace", "flail", "sling", "thrown", "polearm"],
|
||||||
|
"sling": ["axe", "hammer", "mace", "flail", "bow", "thrown", "polearm"],
|
||||||
|
"thrown": ["axe", "hammer", "mace", "flail", "bow", "sling", "polearm"],
|
||||||
|
"polearm": ["axe", "hammer", "mace", "flail", "bow", "sling", "thrown"]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export { default as PrismRPGActor } from "./actor.mjs"
|
||||||
|
export { default as PrismRPGItem } from "./item.mjs"
|
||||||
|
export { default as PrismRPGRoll } from "./roll.mjs"
|
||||||
|
export { default as PrismRPGChatMessage } from "./chat-message.mjs"
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import PrismRPGUtils from "../utils.mjs"
|
||||||
|
export default class PrismRPGActor extends Actor {
|
||||||
|
|
||||||
|
static async create(data, options) {
|
||||||
|
|
||||||
|
// Case of compendium global import
|
||||||
|
if (data instanceof Array) {
|
||||||
|
return super.create(data, options);
|
||||||
|
}
|
||||||
|
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
|
||||||
|
if (data.items) {
|
||||||
|
let actor = super.create(data, options);
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'character') {
|
||||||
|
const skills = await PrismRPGUtils.loadCompendium("fvtt-prism-rpg.lf-skills")
|
||||||
|
data.items = data.items || []
|
||||||
|
for (let skill of skills) {
|
||||||
|
if (skill.system.category === "layperson") {
|
||||||
|
data.items.push(skill.toObject())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.create(data, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preCreate(data, options, user) {
|
||||||
|
await super._preCreate(data, options, user)
|
||||||
|
|
||||||
|
// Configure prototype token settings
|
||||||
|
const prototypeToken = {}
|
||||||
|
if (this.type === "character") {
|
||||||
|
Object.assign(prototypeToken, {
|
||||||
|
sight: { enabled: true },
|
||||||
|
actorLink: true,
|
||||||
|
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY,
|
||||||
|
})
|
||||||
|
this.updateSource({ prototypeToken })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************************/
|
||||||
|
getBestWeaponClassSkill(skills, rollType, multiplier = 1.0) {
|
||||||
|
let maxValue = 0
|
||||||
|
let goodSkill = skills[0]
|
||||||
|
for (let s of skills) {
|
||||||
|
if (rollType === "weapon-attack") {
|
||||||
|
if (s.system.weaponBonus.attack > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.attack)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rollType === "weapon-defense") {
|
||||||
|
if (s.system.weaponBonus.defense > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.defense)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rollType.includes("weapon-damage")) {
|
||||||
|
if (s.system.weaponBonus.damage > maxValue) {
|
||||||
|
maxValue = Number(s.system.weaponBonus.damage)
|
||||||
|
goodSkill = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goodSkill.weaponSkillModifier = maxValue * multiplier
|
||||||
|
return goodSkill
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************************/
|
||||||
|
async applyDamage(hpLoss) {
|
||||||
|
let hp = this.system.hp.value + hpLoss
|
||||||
|
if (hp < 0) {
|
||||||
|
hp = 0
|
||||||
|
}
|
||||||
|
this.update({ "system.hp.value": hp })
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *************************************************/
|
||||||
|
async prepareRoll(rollType, rollKey, rollDice ) {
|
||||||
|
console.log("Preparing roll", rollType, rollKey, rollDice)
|
||||||
|
let rollTarget
|
||||||
|
switch (rollType) {
|
||||||
|
case "granted":
|
||||||
|
rollTarget = {
|
||||||
|
name: rollKey,
|
||||||
|
formula: foundry.utils.duplicate(this.system.granted[rollKey]),
|
||||||
|
rollKey: rollKey
|
||||||
|
}
|
||||||
|
if ( rollTarget.formula === "" || rollTarget.formula === undefined) {
|
||||||
|
rollTarget.formula = 0
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "challenge":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.system.challenges[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "save":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.system.saves[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
rollTarget.rollDice = rollDice
|
||||||
|
break
|
||||||
|
case "spell":
|
||||||
|
rollTarget = this.items.find((i) => i.type === "spell" && i.id === rollKey)
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "miracle":
|
||||||
|
rollTarget = this.items.find((i) => i.type === "miracle" && i.id === rollKey)
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "skill":
|
||||||
|
rollTarget = this.items.find((i) => i.type === "skill" && i.id === rollKey)
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
if (rollTarget.system.category === "weapon") {
|
||||||
|
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.rollFromWeapon"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case "spell-attack":
|
||||||
|
case "spell-power":
|
||||||
|
case "miracle-attack":
|
||||||
|
case "miracle-power":
|
||||||
|
rollTarget = this.items.find((i) => (i.type === "miracle" || i.type == "spell") && i.id === rollKey)
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "shield-roll": {
|
||||||
|
rollTarget = this.items.find((i) => i.type === "shield" && i.id === rollKey)
|
||||||
|
let shieldSkill = this.items.find((i) => i.type === "skill" && i.name.toLowerCase() === rollTarget.name.toLowerCase())
|
||||||
|
rollTarget.skill = shieldSkill
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "weapon-damage-small":
|
||||||
|
case "weapon-damage-medium":
|
||||||
|
case "weapon-attack":
|
||||||
|
case "weapon-defense": {
|
||||||
|
let weapon = this.items.find((i) => i.type === "weapon" && i.id === rollKey)
|
||||||
|
let skill
|
||||||
|
let skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
|
||||||
|
} else {
|
||||||
|
skills = this.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
|
||||||
|
} else {
|
||||||
|
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
|
||||||
|
} else {
|
||||||
|
skills = this.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!weapon || !skill) {
|
||||||
|
console.error("Weapon or skill not found", weapon, skill)
|
||||||
|
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rollTarget = skill
|
||||||
|
rollTarget.weapon = weapon
|
||||||
|
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
rollTarget.combat = foundry.utils.duplicate(this.system.combat)
|
||||||
|
if ( rollType === "weapon-damage-small" || rollType === "weapon-damage-medium") {
|
||||||
|
rollTarget.grantedDice = this.system.granted.damageDice
|
||||||
|
}
|
||||||
|
if ( rollType === "weapon-attack") {
|
||||||
|
rollTarget.grantedDice = this.system.granted.attackDice
|
||||||
|
}
|
||||||
|
if ( rollType === "weapon-defense") {
|
||||||
|
rollTarget.grantedDice = this.system.granted.defenseDice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ui.notifications.error(game.i18n.localize("PRISMRPG.Notifications.rollTypeNotFound") + String(rollType))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all cases
|
||||||
|
rollTarget.magicUser = this.system.biodata.magicUser
|
||||||
|
rollTarget.actorModifiers = foundry.utils.duplicate(this.system.modifiers)
|
||||||
|
rollTarget.actorLevel = this.system.biodata.level
|
||||||
|
await this.system.roll(rollType, rollTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import PrismRPGRoll from "./roll.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGChatMessage extends ChatMessage {
|
||||||
|
|
||||||
|
async _renderRollContent(messageData) {
|
||||||
|
const data = messageData.message
|
||||||
|
if (this.rolls[0] instanceof PrismRPGRoll) {
|
||||||
|
const isPrivate = !this.isContentVisible
|
||||||
|
// _renderRollHTML va appeler render sur tous les rolls
|
||||||
|
const rollHTML = await this._renderRollHTML(isPrivate)
|
||||||
|
if (isPrivate) {
|
||||||
|
data.flavor = game.i18n.format("CHAT.PrivateRollContent", { user: this.user.name })
|
||||||
|
messageData.isWhisper = false
|
||||||
|
messageData.alias = this.user.name
|
||||||
|
}
|
||||||
|
data.content = `<section class="dice-rolls">${rollHTML}</section>`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return super._renderRollContent(messageData)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export const defaultItemImg = {
|
||||||
|
weapon: "systems/fvtt-prism-rpg/assets/icons/icon_weapon.webp",
|
||||||
|
armor: "systems/fvtt-prism-rpg/assets/icons/icon_armor.webp",
|
||||||
|
equipment: "systems/fvtt-prism-rpg/assets/icons/icon_equipment.webp",
|
||||||
|
skill: "systems/fvtt-prism-rpg/assets/icons/icon_skill.webp",
|
||||||
|
gift: "systems/fvtt-prism-rpg/assets/icons/icon_gift.webp",
|
||||||
|
vulnerability: "systems/fvtt-prism-rpg/assets/icons/icon_vulnerability.webp",
|
||||||
|
shield: "systems/fvtt-prism-rpg/assets/icons/icon_shield.webp",
|
||||||
|
spell: "systems/fvtt-prism-rpg/assets/icons/icon_spell.webp",
|
||||||
|
miracle: "systems/fvtt-prism-rpg/assets/icons/icon_miracle.webp"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PrismRPGItem extends Item {
|
||||||
|
constructor(data, context) {
|
||||||
|
if (!data.img) {
|
||||||
|
data.img = defaultItemImg[data.type];
|
||||||
|
}
|
||||||
|
super(data, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Enricher qui permet de transformer un texte en un lien de lancer de dés
|
||||||
|
* Pour une syntaxe de type @jet[x]{y}(z) avec x la caractéristique, y le titre et z l'avantage
|
||||||
|
* x de type rob, dex, int, per, vol pour les caractéristiques
|
||||||
|
* et de type oeil, verbe, san, bourse, magie pour les ressources
|
||||||
|
* y est le titre du jet et permet de décrire l'action
|
||||||
|
* z est l'avantage du jet, avec pour valeurs possibles : --, -, +, ++
|
||||||
|
*/
|
||||||
|
export function setupTextEnrichers() {
|
||||||
|
CONFIG.TextEditor.enrichers = CONFIG.TextEditor.enrichers.concat([
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
pattern: /\@jet\[(.+?)\]{(.*?)}\((.*?)\)/gm,
|
||||||
|
enricher: async (match, options) => {
|
||||||
|
const a = document.createElement("a")
|
||||||
|
a.classList.add("ask-roll-journal")
|
||||||
|
const target = match[1]
|
||||||
|
const title = match[2]
|
||||||
|
const avantage = match[3]
|
||||||
|
|
||||||
|
let type = "resource"
|
||||||
|
if (["rob", "dex", "int", "per", "vol"].includes(target)) {
|
||||||
|
type = "save"
|
||||||
|
}
|
||||||
|
|
||||||
|
let rollAvantage = "normal"
|
||||||
|
if (avantage) {
|
||||||
|
switch (avantage) {
|
||||||
|
case "++":
|
||||||
|
rollAvantage = "++"
|
||||||
|
break
|
||||||
|
case "+":
|
||||||
|
rollAvantage = "+"
|
||||||
|
break
|
||||||
|
case "-":
|
||||||
|
rollAvantage = "-"
|
||||||
|
break
|
||||||
|
case "--":
|
||||||
|
rollAvantage = "--"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.dataset.rollType = type
|
||||||
|
a.dataset.rollTarget = target
|
||||||
|
a.dataset.rollTitle = title
|
||||||
|
a.dataset.rollAvantage = rollAvantage
|
||||||
|
a.innerHTML = `
|
||||||
|
<i class="fas fa-dice-d20"></i> ${getLibelle(target)}${rollAvantage !== "normal" ? rollAvantage : ""}
|
||||||
|
`
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
const mapLibelles = {
|
||||||
|
rob: "ROB",
|
||||||
|
dex: "DEX",
|
||||||
|
int: "INT",
|
||||||
|
per: "PER",
|
||||||
|
vol: "VOL",
|
||||||
|
oeil: "OEIL",
|
||||||
|
verbe: "VERBE",
|
||||||
|
san: "SANTE MENTALE",
|
||||||
|
bourse: "BOURSE",
|
||||||
|
magie: "MAGIE",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne le libellé associé à la valeur qui sera affiché dans le journal
|
||||||
|
* @param {string} value
|
||||||
|
*/
|
||||||
|
function getLibelle(value) {
|
||||||
|
if (mapLibelles[value]) {
|
||||||
|
return mapLibelles[value]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
export class Macros {
|
||||||
|
/**
|
||||||
|
* Creates a macro based on the type of data dropped onto the hotbar.
|
||||||
|
*
|
||||||
|
* @param {Object} dropData The data object representing the item dropped.
|
||||||
|
* @param {string} dropData.type The type of the dropped item (e.g., "Actor", "JournalEntry", "roll").
|
||||||
|
* @param {string} dropData.uuid The UUID of the dropped item.
|
||||||
|
* @param {string} [dropData.actorId] The ID of the actor (required if type is "roll").
|
||||||
|
* @param {string} [dropData.rollType] The type of roll (required if type is "roll").
|
||||||
|
* @param {string} [dropData.rollTarget] The target of the roll (required if type is "roll").
|
||||||
|
* @param {string} [dropData.value] The value of the roll (required if type is "roll").
|
||||||
|
* @param {number} slot The hotbar slot where the macro will be created.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the macro is created.
|
||||||
|
*/
|
||||||
|
static createPrismRPGMacro = async function (dropData, slot) {
|
||||||
|
switch (dropData.type) {
|
||||||
|
case "Actor":
|
||||||
|
const actor = await fromUuid(dropData.uuid)
|
||||||
|
const actorCommand = `game.actors.get("${actor.id}").sheet.render(true)`
|
||||||
|
this.createMacro(slot, actor.name, actorCommand, actor.img)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "JournalEntry":
|
||||||
|
const journal = await fromUuid(dropData.uuid)
|
||||||
|
const journalCommand = `game.journal.get("${journal.id}").sheet.render(true)`
|
||||||
|
this.createMacro(slot, journal.name, journalCommand, journal.img ? journal.img : "icons/svg/book.svg")
|
||||||
|
break
|
||||||
|
|
||||||
|
case "roll":
|
||||||
|
const rollCommand =
|
||||||
|
dropData.rollType === "save"
|
||||||
|
? `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}', '=');`
|
||||||
|
: `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}');`
|
||||||
|
const rollName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${game.i18n.localize(`TENEBRIS.Manager.${dropData.rollTarget}`)}`
|
||||||
|
this.createMacro(slot, rollName, rollCommand, "icons/svg/d20-grey.svg")
|
||||||
|
break
|
||||||
|
|
||||||
|
case "rollDamage":
|
||||||
|
const weapon = game.actors.get(dropData.actorId).items.get(dropData.rollTarget)
|
||||||
|
const rollDamageCommand = `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollType}', '${dropData.rollTarget}');`
|
||||||
|
const rollDamageName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${weapon.name}`
|
||||||
|
this.createMacro(slot, rollDamageName, rollDamageCommand, weapon.img)
|
||||||
|
break
|
||||||
|
|
||||||
|
case "rollAttack":
|
||||||
|
const rollAttackCommand = `game.actors.get('${dropData.actorId}').system.roll('${dropData.rollValue}', '${dropData.rollTarget}');`
|
||||||
|
const rollAttackName = `${game.i18n.localize("TENEBRIS.Label.jet")} ${dropData.rollTarget}`
|
||||||
|
this.createMacro(slot, rollAttackName, rollAttackCommand, "icons/svg/d20-grey.svg")
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Handle other cases or do nothing
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a macro
|
||||||
|
* All macros are flaged with a tenebris.macro flag at true
|
||||||
|
* @param {*} slot
|
||||||
|
* @param {*} name
|
||||||
|
* @param {*} command
|
||||||
|
* @param {*} img
|
||||||
|
*/
|
||||||
|
static createMacro = async function (slot, name, command, img) {
|
||||||
|
let macro = game.macros.contents.find((m) => m.name === name && m.command === command)
|
||||||
|
if (!macro) {
|
||||||
|
macro = await Macro.create(
|
||||||
|
{
|
||||||
|
name: name,
|
||||||
|
type: "script",
|
||||||
|
img: img,
|
||||||
|
command: command,
|
||||||
|
flags: { "tenebris.macro": true },
|
||||||
|
},
|
||||||
|
{ displaySheet: false },
|
||||||
|
)
|
||||||
|
game.user.assignHotbarMacro(macro, slot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export { default as PrismRPGCharacter } from "./character.mjs"
|
||||||
|
export { default as PrismRPGMonster } from "./monster.mjs"
|
||||||
|
export { default as PrismRPGWeapon } from "./weapon.mjs"
|
||||||
|
export { default as PrismRPGSpell } from "./spell.mjs"
|
||||||
|
export { default as PrismRPGSkill } from "./skill.mjs"
|
||||||
|
export { default as PrismRPGArmor } from "./armor.mjs"
|
||||||
|
export { default as PrismRPGShield } from "./shield.mjs"
|
||||||
|
export { default as PrismRPGGift } from "./gift.mjs"
|
||||||
|
export { default as PrismRPGVulnerability } from "./vulnerability.mjs"
|
||||||
|
export { default as PrismRPGEquipment } from "./equipment.mjs"
|
||||||
|
export { default as PrismRPGMiracle } from "./miracle.mjs"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
export default class PrismRPGArmor extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.armorType = new fields.StringField({ required: true, initial: "light", choices: SYSTEM.ARMOR_TYPE })
|
||||||
|
schema.defense = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: -50 })
|
||||||
|
schema.maximumMovement = new fields.StringField({ ...requiredInteger, required: true, initial: "" })
|
||||||
|
schema.hp = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.damageReduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
|
||||||
|
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.equipped = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
schema.isHelmet = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Armor"]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
import PrismRPGRoll from "../documents/roll.mjs"
|
||||||
|
import PrismRPGUtils from "../utils.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGCharacter extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
|
// Carac
|
||||||
|
const characteristicField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
|
||||||
|
percent: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 100 }),
|
||||||
|
attackMod: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||||
|
defenseMod: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.characteristics = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.CHARACTERISTICS).reduce((obj, characteristic) => {
|
||||||
|
obj[characteristic.id] = characteristicField(characteristic.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save
|
||||||
|
const saveField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
schema.saves = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.SAVES).reduce((obj, save) => {
|
||||||
|
obj[save.id] = saveField(save.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Challenges
|
||||||
|
const challengeField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.StringField({ initial: "0", required: true, nullable: false }),
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
schema.challenges = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.CHALLENGES).reduce((obj, save) => {
|
||||||
|
obj[save.id] = challengeField(save.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
const woundFieldSchema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
duration: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
description: new fields.StringField({ initial: "", required: false, nullable: true }),
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.hp = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||||
|
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
wounds: new fields.ArrayField(new fields.SchemaField(woundFieldSchema), {
|
||||||
|
initial: [{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
|
||||||
|
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 },
|
||||||
|
{ description: "", value: 0, duration: 0 }, { description: "", value: 0, duration: 0 }], min: 8
|
||||||
|
}),
|
||||||
|
damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.perception = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.grit = new fields.SchemaField({
|
||||||
|
starting: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.luck = new fields.SchemaField({
|
||||||
|
earned: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.granted = new fields.SchemaField({
|
||||||
|
attackDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
|
||||||
|
defenseDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES }),
|
||||||
|
damageDice: new fields.StringField({ required: true, nullable: false, initial: "0", choices: SYSTEM.GRANTED_DICE_CHOICES })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.movement = new fields.SchemaField({
|
||||||
|
walk: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
jog: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
sprint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
run: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
armorAdjust: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
schema.jump = new fields.SchemaField({
|
||||||
|
broad: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
running: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
vertical: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
schema.biodata = new fields.SchemaField({
|
||||||
|
class: new fields.StringField({ required: true, initial: "untrained", choices: SYSTEM.CHAR_CLASSES }),
|
||||||
|
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }),
|
||||||
|
mortal: new fields.StringField({ required: true, initial: "mankind", choices: SYSTEM.MORTAL_CHOICES }),
|
||||||
|
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
age: new fields.NumberField({ ...requiredInteger, initial: 15, min: 6 }),
|
||||||
|
height: new fields.NumberField({ ...requiredInteger, initial: 170, min: 10 }),
|
||||||
|
weight: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
magicUser: new fields.BooleanField({ initial: false }),
|
||||||
|
clericUser: new fields.BooleanField({ initial: false }),
|
||||||
|
hpPerLevel: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.modifiers = new fields.SchemaField({
|
||||||
|
levelSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
saveModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
levelMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
intSpellModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
chaMiracleModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.developmentPoints = new fields.SchemaField({
|
||||||
|
total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
remaining: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.spellMiraclePoints = new fields.SchemaField({
|
||||||
|
total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
used: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.aetherPoints = new fields.SchemaField({
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.divinityPoints = new fields.SchemaField({
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.combat = new fields.SchemaField({
|
||||||
|
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
rangedAttackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
defenseBonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const moneyField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
schema.moneys = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.MONEY).reduce((obj, save) => {
|
||||||
|
obj[save.id] = moneyField(save.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Character"]
|
||||||
|
|
||||||
|
static migrateData(data) {
|
||||||
|
if (data?.biodata?.mortal) {
|
||||||
|
if (!SYSTEM.MORTAL_CHOICES[data.biodata.mortal]) {
|
||||||
|
for (let key in SYSTEM.MORTAL_CHOICES) {
|
||||||
|
let mortal = SYSTEM.MORTAL_CHOICES[key]
|
||||||
|
if (mortal.label.toLowerCase() === data.biodata.mortal.toLowerCase()) {
|
||||||
|
data.biodata.mortal = mortal.id
|
||||||
|
}
|
||||||
|
if (data.biodata.mortal.toLowerCase().includes("shire")) {
|
||||||
|
data.biodata.mortal = "halflings"
|
||||||
|
}
|
||||||
|
if (data.biodata.mortal.toLowerCase().includes("human")) {
|
||||||
|
data.biodata.mortal = "mankind"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!SYSTEM.MORTAL_CHOICES[data.biodata.mortal]) {
|
||||||
|
console.warn("Lethal Fantasy | Migrate data: Mortal not found, forced to mankind", data.biodata.mortal)
|
||||||
|
data.biodata.mortal = "mankind"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.migrateData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData();
|
||||||
|
let grit = 0
|
||||||
|
for (let c in this.characteristics) {
|
||||||
|
if (SYSTEM.CHARACTERISTICS_MAJOR[c.id]) {
|
||||||
|
grit += this.characteristics[c].value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modifiers.saveModifier = Math.floor((Number(this.biodata.level) / 5))
|
||||||
|
this.modifiers.levelSpellModifier = Math.floor((Number(this.biodata.level) / 5))
|
||||||
|
this.modifiers.levelMiracleModifier = Math.floor((Number(this.biodata.level) / 5))
|
||||||
|
|
||||||
|
this.grit.starting = Math.round(grit / 6)
|
||||||
|
|
||||||
|
let strDef = SYSTEM.CHARACTERISTICS_TABLES.str.find(s => s.value === this.characteristics.str.value)
|
||||||
|
this.challenges.str.value = strDef.challenge
|
||||||
|
|
||||||
|
let intDef = SYSTEM.CHARACTERISTICS_TABLES.int.find(s => s.value === this.characteristics.int.value)
|
||||||
|
this.modifiers.intSpellModifier = intDef.arkane_casting_mod
|
||||||
|
|
||||||
|
let dexDef = SYSTEM.CHARACTERISTICS_TABLES.dex.find(s => s.value === this.characteristics.dex.value)
|
||||||
|
this.challenges.agility.value = dexDef.challenge
|
||||||
|
this.saves.dodge.value = dexDef.dodge + this.modifiers.saveModifier
|
||||||
|
|
||||||
|
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find(s => s.value === this.characteristics.wis.value)
|
||||||
|
this.saves.will.value = wisDef.willpower_save + this.modifiers.saveModifier
|
||||||
|
|
||||||
|
let chaDef = SYSTEM.CHARACTERISTICS_TABLES.cha.find(s => s.value === this.characteristics.cha.value)
|
||||||
|
this.modifiers.chaMiracleModifier = chaDef.divine_miracle_bonus
|
||||||
|
|
||||||
|
let conDef = SYSTEM.CHARACTERISTICS_TABLES.con.find(s => s.value === this.characteristics.con.value)
|
||||||
|
this.saves.pain.value = conDef.pain_save + this.modifiers.saveModifier
|
||||||
|
this.saves.toughness.value = conDef.toughness_save + this.modifiers.saveModifier
|
||||||
|
this.challenges.dying.value = conDef.stabilization_dice
|
||||||
|
|
||||||
|
this.saves.contagion.value = this.characteristics.con.value;// + this.modifiers.saveModifier
|
||||||
|
this.saves.poison.value = this.characteristics.con.value; // + this.modifiers.saveModifier
|
||||||
|
|
||||||
|
this.combat.attackModifier = 0
|
||||||
|
for (let chaKey of SYSTEM.CHARACTERISTIC_ATTACK) {
|
||||||
|
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
|
||||||
|
this.combat.attackModifier += chaDef.attack
|
||||||
|
}
|
||||||
|
this.combat.rangedAttackModifier = 0
|
||||||
|
for (let chaKey of SYSTEM.CHARACTERISTIC_RANGED_ATTACK) {
|
||||||
|
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
|
||||||
|
this.combat.rangedAttackModifier += chaDef.attack
|
||||||
|
}
|
||||||
|
|
||||||
|
this.combat.defenseBonus = SYSTEM.MORTAL_CHOICES[this.biodata.mortal]?.defenseBonus || 0
|
||||||
|
this.combat.defenseModifier = this.combat.defenseBonus
|
||||||
|
for (let chaKey of SYSTEM.CHARACTERISTIC_DEFENSE) {
|
||||||
|
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
|
||||||
|
this.combat.defenseModifier += chaDef.defense
|
||||||
|
}
|
||||||
|
|
||||||
|
this.combat.damageModifier = 0
|
||||||
|
for (let chaKey of SYSTEM.CHARACTERISTIC_DAMAGE) {
|
||||||
|
let chaDef = SYSTEM.CHARACTERISTICS_TABLES[chaKey].find(s => s.value === this.characteristics[chaKey].value)
|
||||||
|
this.combat.damageModifier += chaDef.damage
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolls a dice for a character.
|
||||||
|
* @param {("save"|"resource|damage")} rollType The type of the roll.
|
||||||
|
* @param {number} rollTarget The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
|
||||||
|
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
||||||
|
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
||||||
|
*/
|
||||||
|
async roll(rollType, rollTarget) {
|
||||||
|
const hasTarget = false
|
||||||
|
let roll = await PrismRPGRoll.prompt({
|
||||||
|
rollType,
|
||||||
|
rollTarget,
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
hasTarget,
|
||||||
|
target: false
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollInitiative(combatId = undefined, combatantId = undefined) {
|
||||||
|
const hasTarget = false
|
||||||
|
let actorClass = this.biodata.class;
|
||||||
|
|
||||||
|
let wisDef = SYSTEM.CHARACTERISTICS_TABLES.wis.find((c) => c.value === this.characteristics.wis.value)
|
||||||
|
let maxInit = Number(wisDef.init_cap) || 1000
|
||||||
|
|
||||||
|
let roll = await PrismRPGRoll.promptInitiative({
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
combatId,
|
||||||
|
combatantId,
|
||||||
|
actorClass,
|
||||||
|
maxInit,
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollProgressionDice(combatId, combatantId, rollProgressionCount) {
|
||||||
|
|
||||||
|
// Get all weapons from the actor
|
||||||
|
let weapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "melee")
|
||||||
|
let weaponsChoices = weapons.map(w => { return { id: w.id, name: `${w.name} (${w.system.combatProgressionDice.toUpperCase()})`, combatProgressionDice: w.system.combatProgressionDice.toUpperCase() } })
|
||||||
|
let rangeWeapons = this.parent.items.filter(i => i.type === "weapon" && i.system.weaponType === "ranged")
|
||||||
|
for (let w of rangeWeapons) {
|
||||||
|
weaponsChoices.push({ id: `${w.id}simpleAim`, name: `${w.name} (Simple Aim: ${w.system.speed.simpleAim.toUpperCase()})`, combatProgressionDice: w.system.speed.simpleAim.toUpperCase() })
|
||||||
|
weaponsChoices.push({ id: `${w.id}carefulAim`, name: `${w.name} (Careful Aim: ${w.system.speed.carefulAim.toUpperCase()})`, combatProgressionDice: w.system.speed.carefulAim.toUpperCase() })
|
||||||
|
weaponsChoices.push({ id: `${w.id}focusedAim`, name: `${w.name} (Focused Aim: ${w.system.speed.focusedAim.toUpperCase()})`, combatProgressionDice: w.system.speed.focusedAim.toUpperCase() })
|
||||||
|
}
|
||||||
|
if (this.biodata.magicUser || this.biodata.clericUser) {
|
||||||
|
let spells = this.parent.items.filter(i => i.type === "spell" || i.type === "miracle")
|
||||||
|
for (let s of spells) {
|
||||||
|
let title = ""
|
||||||
|
let formula = ""
|
||||||
|
if (s.type === "spell") {
|
||||||
|
let dice = PrismRPGUtils.getLethargyDice(s.system.level)
|
||||||
|
title = `${s.name} (Casting time: ${s.system.castingTime}, Lethargy: ${dice})`
|
||||||
|
formula = `${s.system.castingTime}+${dice}`
|
||||||
|
} else {
|
||||||
|
title = `${s.name} (Prayer time: ${s.system.prayerTime})`
|
||||||
|
formula = `${s.system.prayerTime}`
|
||||||
|
}
|
||||||
|
weaponsChoices.push({ id: s.id, name: title, combatProgressionDice: formula })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let roll = await PrismRPGRoll.promptCombatAction({
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
weaponsChoices,
|
||||||
|
combatId,
|
||||||
|
combatantId,
|
||||||
|
rollProgressionCount,
|
||||||
|
type: "progression",
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGEquipment extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.category = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.EQUIPMENT_CATEGORIES })
|
||||||
|
|
||||||
|
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.hi = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.medium = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.lo = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Equipment"]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export default class PrismRPGGift extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Gift"]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
export default class PrismRPGMiracle extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({
|
||||||
|
required: false,
|
||||||
|
blank: true,
|
||||||
|
initial: "",
|
||||||
|
textSearch: true,
|
||||||
|
})
|
||||||
|
schema.level = new fields.NumberField({
|
||||||
|
...requiredInteger,
|
||||||
|
initial: 1,
|
||||||
|
min: 1,
|
||||||
|
max: 25,
|
||||||
|
})
|
||||||
|
schema.components = new fields.SchemaField({
|
||||||
|
verbal: new fields.BooleanField(),
|
||||||
|
somatic: new fields.BooleanField(),
|
||||||
|
material: new fields.BooleanField(),
|
||||||
|
catalyst: new fields.BooleanField(),
|
||||||
|
religious: new fields.BooleanField()
|
||||||
|
})
|
||||||
|
schema.prayerTime = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.miracleRange = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.areaAffected = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.duration = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.savingThrow = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.materialComponent = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.catalyst = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.miracleType = new fields.StringField({ required: true, initial: "combat", choices: SYSTEM.MIRACLE_TYPES })
|
||||||
|
|
||||||
|
schema.attackRoll = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.powerRoll = new fields.StringField({ required: true, initial: "" })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Miracle"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
import PrismRPGRoll from "../documents/roll.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGMonster extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
|
||||||
|
// Carac
|
||||||
|
const characteristicField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 3, min: 0 }),
|
||||||
|
percent: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 100 }),
|
||||||
|
attackMod: new fields.NumberField({ ...requiredInteger, initial: 0 }),
|
||||||
|
defenseMod: new fields.NumberField({ ...requiredInteger, initial: 0 })
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
|
||||||
|
schema.characteristics = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.MONSTER_CHARACTERISTICS).reduce((obj, characteristic) => {
|
||||||
|
obj[characteristic.id] = characteristicField(characteristic.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save
|
||||||
|
const saveField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
schema.saves = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.MONSTER_SAVES).reduce((obj, save) => {
|
||||||
|
obj[save.id] = saveField(save.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resist
|
||||||
|
const resistField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
value: new fields.StringField({ initial: "0", required: true, nullable: false }),
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
schema.resists = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.MONSTER_RESIST).reduce((obj, save) => {
|
||||||
|
obj[save.id] = resistField(save.label)
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
|
||||||
|
schema.hp = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||||
|
average: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
|
||||||
|
damageResistance: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
painDamage: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
const attackField = (label) => {
|
||||||
|
const schema = {
|
||||||
|
key: new fields.StringField({ required: true, nullable: false, initial: `attack${label}` }),
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: `Attack ${label}` }),
|
||||||
|
attackScore: new fields.NumberField({ ...requiredInteger, initial: Number(label), min: 0 }),
|
||||||
|
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
damageDice: new fields.StringField({ required: true, nullable: false, initial: "1D6" }),
|
||||||
|
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
enabled: new fields.BooleanField({ initial: true, required: true, nullable: false }),
|
||||||
|
}
|
||||||
|
return new fields.SchemaField(schema, { label })
|
||||||
|
}
|
||||||
|
// Add 4 attackFields in an attack schema
|
||||||
|
schema.attacks = new fields.SchemaField({
|
||||||
|
attack1: attackField("1"),
|
||||||
|
attack2: attackField("2"),
|
||||||
|
attack3: attackField("3"),
|
||||||
|
attack4: attackField("4"),
|
||||||
|
attack5: attackField("5"),
|
||||||
|
attack6: attackField("6"),
|
||||||
|
attack7: attackField("7"),
|
||||||
|
attack8: attackField("8")
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.perception = new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.movement = new fields.SchemaField({
|
||||||
|
walk: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
jog: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
sprint: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
run: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
armorAdjust: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
schema.jump = new fields.SchemaField({
|
||||||
|
broad: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
running: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
vertical: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
schema.biodata = new fields.SchemaField({
|
||||||
|
alignment: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
vision: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
height: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
length: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
weight: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
})
|
||||||
|
schema.combat = new fields.SchemaField({
|
||||||
|
attackModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
defenseModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
damageModifier: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
armorHitPoints: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Monster"]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rolls a dice for a character.
|
||||||
|
* @param {("save"|"resource|damage")} rollType The type of the roll.
|
||||||
|
* @param {number} rollTarget The target value for the roll. Which caracteristic or resource. If the roll is a damage roll, this is the id of the item.
|
||||||
|
* @param {"="|"+"|"++"|"-"|"--"} rollAdvantage If there is an avantage (+), a disadvantage (-), a double advantage (++), a double disadvantage (--) or a normal roll (=).
|
||||||
|
* @returns {Promise<null>} - A promise that resolves to null if the roll is cancelled.
|
||||||
|
*/
|
||||||
|
async roll(rollType, rollTarget) {
|
||||||
|
const hasTarget = false
|
||||||
|
let roll = await PrismRPGRoll.prompt({
|
||||||
|
rollType,
|
||||||
|
rollTarget,
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
hasTarget,
|
||||||
|
target: false
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
async prepareMonsterRoll(rollType, rollKey, rollDice = undefined, tokenId = undefined) {
|
||||||
|
let rollTarget
|
||||||
|
switch (rollType) {
|
||||||
|
case "monster-attack":
|
||||||
|
case "monster-defense":
|
||||||
|
case "monster-damage":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.attacks[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "monster-skill":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.resists[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
break
|
||||||
|
case "save":
|
||||||
|
rollTarget = foundry.utils.duplicate(this.saves[rollKey])
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
rollTarget.rollDice = rollDice
|
||||||
|
break
|
||||||
|
case "weapon-damage-small":
|
||||||
|
case "weapon-damage-medium":
|
||||||
|
case "weapon-attack":
|
||||||
|
case "weapon-defense": {
|
||||||
|
let weapon = this.actor.items.find((i) => i.type === "weapon" && i.id === rollKey)
|
||||||
|
let skill
|
||||||
|
let skills = this.actor.items.filter((i) => i.type === "skill" && i.name.toLowerCase() === weapon.name.toLowerCase())
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
|
||||||
|
} else {
|
||||||
|
skills = this.actor.items.filter((i) => i.type === "skill" && i.name.toLowerCase().replace(" skill", "") === weapon.name.toLowerCase())
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 1.0)
|
||||||
|
} else {
|
||||||
|
skills = this.actor.items.filter((i) => i.type === "skill" && i.system.weaponClass === weapon.system.weaponClass)
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 0.5)
|
||||||
|
} else {
|
||||||
|
skills = this.actor.items.filter((i) => i.type === "skill" && i.system.weaponClass.includes(SYSTEM.WEAPON_CATEGORIES[weapon.system.weaponClass]))
|
||||||
|
if (skills.length > 0) {
|
||||||
|
skill = this.getBestWeaponClassSkill(skills, rollType, 0.25)
|
||||||
|
} else {
|
||||||
|
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!weapon || !skill) {
|
||||||
|
console.error("Weapon or skill not found", weapon, skill)
|
||||||
|
ui.notifications.warn(game.i18n.localize("PRISMRPG.Notifications.skillNotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rollTarget = skill
|
||||||
|
rollTarget.weapon = weapon
|
||||||
|
rollTarget.weaponSkillModifier = skill.weaponSkillModifier
|
||||||
|
rollTarget.rollKey = rollKey
|
||||||
|
rollTarget.combat = foundry.utils.duplicate(this.combat)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ui.notifications.error(game.i18n.localize("PRISMRPG.Notifications.rollTypeNotFound") + String(rollType))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// In all cases
|
||||||
|
rollTarget.tokenId = tokenId
|
||||||
|
console.log(rollTarget)
|
||||||
|
await this.roll(rollType, rollTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollInitiative(combatId = undefined, combatantId = undefined) {
|
||||||
|
const hasTarget = false
|
||||||
|
|
||||||
|
let maxInit = 100
|
||||||
|
|
||||||
|
let roll = await PrismRPGRoll.promptInitiative({
|
||||||
|
actorId: this.parent.id,
|
||||||
|
actorName: this.parent.name,
|
||||||
|
actorImage: this.parent.img,
|
||||||
|
combatId,
|
||||||
|
combatantId,
|
||||||
|
actorClass: "fighter",
|
||||||
|
maxInit,
|
||||||
|
})
|
||||||
|
if (!roll) return null
|
||||||
|
|
||||||
|
await roll.toMessage({}, { rollMode: roll.options.rollMode })
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollProgressionDice(combatId, combatantId) {
|
||||||
|
|
||||||
|
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
|
||||||
|
const fieldRollMode = new foundry.data.fields.StringField({
|
||||||
|
choices: rollModes,
|
||||||
|
blank: false,
|
||||||
|
default: "public",
|
||||||
|
})
|
||||||
|
|
||||||
|
let roll = new Roll("1D12")
|
||||||
|
await roll.evaluate()
|
||||||
|
let combatant = game.combats.get(combatId)?.combatants?.get(combatantId)
|
||||||
|
|
||||||
|
let msg = await roll.toMessage({ flavor: `Progression Roll for ${this.parent.name}` })
|
||||||
|
if (game?.dice3d) {
|
||||||
|
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasAttack = false
|
||||||
|
for (let key in this.attacks) {
|
||||||
|
let attack = this.attacks[key]
|
||||||
|
if (attack.enabled && attack.attackScore > 0 && attack.attackScore === roll.total) {
|
||||||
|
hasAttack = true
|
||||||
|
let message = game.i18n.format("PRISMRPG.Notifications.messageProgressionOKMonster", { isMonster: true, name: this.parent.name, weapon: attack.name, roll: roll.total })
|
||||||
|
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||||
|
let token = combatant?.token
|
||||||
|
this.prepareMonsterRoll("monster-attack", key, undefined, token?.id)
|
||||||
|
if (token?.object) {
|
||||||
|
token.object?.control({ releaseOthers: true });
|
||||||
|
return canvas.animatePan(token.object.center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasAttack) {
|
||||||
|
let message = game.i18n.format("PRISMRPG.Notifications.messageProgressionKOMonster", { isMonster: true, name: this.parent.name, roll: roll.total })
|
||||||
|
ChatMessage.create({ content: message, speaker: ChatMessage.getSpeaker({ actor: this.parent }) })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
export default class PrismRPGShield extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.defense = new fields.StringField({required: true, initial: "d4", choices: SYSTEM.SHIELD_DEFENSE_DICE})
|
||||||
|
schema.movementreduction = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.hascover = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
schema.standing = new fields.SchemaField({
|
||||||
|
min: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.crouching = new fields.SchemaField({
|
||||||
|
min: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.destruction = new fields.SchemaField({
|
||||||
|
bashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
slashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
piercing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
schema.autodestruction = new fields.SchemaField({
|
||||||
|
bashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
slashing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
piercing: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
|
||||||
|
schema.equipped = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Shield"]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
import { CATEGORY } from "../config/skill.mjs"
|
||||||
|
export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.category = new fields.StringField({ required: true, initial: "layperson", choices: SYSTEM.SKILL_CATEGORY })
|
||||||
|
schema.base = new fields.StringField({ required: true, initial: "WIS" })
|
||||||
|
schema.bonus = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
|
||||||
|
schema.classesCost = new fields.SchemaField(
|
||||||
|
Object.values(SYSTEM.CHAR_CLASSES_DEFINES).reduce((obj, pcClass) => {
|
||||||
|
obj[pcClass.id] = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
return obj
|
||||||
|
}, {}),
|
||||||
|
)
|
||||||
|
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
|
||||||
|
schema.weaponClass = new fields.StringField({ required: true, initial: "shortblade", choices: SYSTEM.WEAPON_CLASS })
|
||||||
|
schema.weaponBonus = new fields.SchemaField({
|
||||||
|
attack: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
defense: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
damage: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Skill"]
|
||||||
|
|
||||||
|
get skillCategory() {
|
||||||
|
return game.i18n.localize(CATEGORY[this.category].label)
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(options) {
|
||||||
|
let isError = super.validate(options)
|
||||||
|
let bonus = this._source.weaponBonus.attack + this._source.weaponBonus.defense + this._source.weaponBonus.damage
|
||||||
|
if (bonus > Math.floor(this._source.skillTotal / 10)) {
|
||||||
|
ui.notifications.error(game.i18n.localize("PRISMRPG.Skill.error.weaponBonus"))
|
||||||
|
isError = true
|
||||||
|
}
|
||||||
|
return isError
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData();
|
||||||
|
this.skillTotal = this.computeBase();
|
||||||
|
if (this.category === "weapon") {
|
||||||
|
this.totalBonus = this.weaponBonus.attack + this.weaponBonus.defense + this.weaponBonus.damage;
|
||||||
|
if (Number(this.skillTotal)) {
|
||||||
|
this.availableBonus = Math.max(Math.floor(this.skillTotal / 10) - 1, 0)
|
||||||
|
} else {
|
||||||
|
this.availableBonus = "N/A"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
computeBase() {
|
||||||
|
let actor = this.parent?.actor;
|
||||||
|
if (!actor) {
|
||||||
|
return `${this.base} + ${String(this.bonus)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.base === "N/A" || this.base === "None") {
|
||||||
|
return this.bonus
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the base value per stat : WIS,DEX,STR,INT,CHA (example)
|
||||||
|
let base = this.base;
|
||||||
|
// Fix errors in the base value
|
||||||
|
base.replace("CHARISMA", "CHA");
|
||||||
|
|
||||||
|
if (base.match(/OR/)) {
|
||||||
|
let baseSplit = base.split("OR");
|
||||||
|
let baseSplitLength = baseSplit.length;
|
||||||
|
if (baseSplitLength > 0) {
|
||||||
|
// Select the max stat value from the parent actor
|
||||||
|
let maxStat = 0;
|
||||||
|
for (let i = 0; i < baseSplitLength; i++) {
|
||||||
|
const stat = baseSplit[i].trim();
|
||||||
|
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
|
||||||
|
if (statValue > maxStat) {
|
||||||
|
maxStat = statValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxStat + this.bonus
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (base.match(/\+/)) {
|
||||||
|
// Split with + calculate the total
|
||||||
|
let baseSplit = base.split("+");
|
||||||
|
let baseSplitLength = baseSplit.length;
|
||||||
|
if (baseSplitLength > 0) {
|
||||||
|
let total = 0;
|
||||||
|
for (let i = 0; i < baseSplitLength; i++) {
|
||||||
|
const stat = baseSplit[i].trim();
|
||||||
|
const statValue = actor.system.characteristics[stat.toLowerCase()]?.value || 0;
|
||||||
|
total += statValue;
|
||||||
|
}
|
||||||
|
return total + this.bonus
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single stat
|
||||||
|
const statValue = actor.system.characteristics[base.trim().toLowerCase()]?.value || 0;
|
||||||
|
return statValue + this.bonus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${this.base} + ${String(this.bonus)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
export default class PrismRPGSpell extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({
|
||||||
|
required: false,
|
||||||
|
blank: true,
|
||||||
|
initial: "",
|
||||||
|
textSearch: true,
|
||||||
|
})
|
||||||
|
schema.level = new fields.NumberField({
|
||||||
|
...requiredInteger,
|
||||||
|
initial: 1,
|
||||||
|
min: 1,
|
||||||
|
max: 25,
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.memorized = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
schema.components = new fields.SchemaField({
|
||||||
|
verbal: new fields.BooleanField(),
|
||||||
|
somatic: new fields.BooleanField(),
|
||||||
|
catalyst: new fields.BooleanField(),
|
||||||
|
material: new fields.BooleanField(),
|
||||||
|
})
|
||||||
|
schema.castingTime = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.spellRange = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.areaAffected = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.duration = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.savingThrow = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.extraAetherPoints = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.materialComponent = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.catalyst = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.criticalType = new fields.StringField({ required: true, initial: "electric", choices : SYSTEM.SPELL_CRITICAL })
|
||||||
|
|
||||||
|
schema.attackRoll = new fields.StringField({ required: true, initial: "" })
|
||||||
|
schema.powerRoll = new fields.StringField({ required: true, initial: "" })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Spell"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default class PrismRPGVulnerability extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
const schema = {}
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.gainedPoints = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Vulnerability"]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { SYSTEM } from "../config/system.mjs"
|
||||||
|
|
||||||
|
export default class PrismRPGSkill extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const schema = {}
|
||||||
|
const requiredInteger = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
schema.description = new fields.HTMLField({ required: true, textSearch: true })
|
||||||
|
schema.weaponType = new fields.StringField({ required: true, initial: "melee", choices: SYSTEM.WEAPON_TYPE })
|
||||||
|
schema.weaponClass = new fields.StringField({ required: true, initial: "shortblade", choices: SYSTEM.WEAPON_CLASS })
|
||||||
|
|
||||||
|
schema.damageType = new fields.SchemaField({
|
||||||
|
typeP: new fields.BooleanField(),
|
||||||
|
typeB: new fields.BooleanField(),
|
||||||
|
typeS: new fields.BooleanField()
|
||||||
|
})
|
||||||
|
schema.damage = new fields.SchemaField({
|
||||||
|
damageS: new fields.StringField({required: true, initial: ""}),
|
||||||
|
damageM: new fields.StringField({required: true, initial: ""})
|
||||||
|
})
|
||||||
|
schema.applyStrengthDamageBonus = new fields.BooleanField({ required: true, initial: true })
|
||||||
|
|
||||||
|
schema.hands = new fields.StringField({ required: true, initial: "1", choices: {"1": "1", "2": "2"} })
|
||||||
|
schema.isAgile = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
schema.defenseMax = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.secondsToAttack = new fields.StringField({required: true, initial: ""})
|
||||||
|
schema.combatProgressionDice = new fields.StringField({required: true, initial: "d4", choices: SYSTEM.COMBAT_PROGRESSION_DICE})
|
||||||
|
|
||||||
|
schema.speed = new fields.SchemaField({
|
||||||
|
simpleAim: new fields.StringField({required: true, initial: ""}),
|
||||||
|
carefulAim: new fields.StringField({required: true, initial: ""}),
|
||||||
|
focusedAim: new fields.StringField({required: true, initial: ""})
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.defense = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.weaponRange = new fields.SchemaField({
|
||||||
|
pointBlank: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
short: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
medium: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
long: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
extreme: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
outOfSkill: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.bonuses = new fields.SchemaField({
|
||||||
|
attackBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
damageBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 }),
|
||||||
|
defenseBonus: new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
schema.encLoad = new fields.NumberField({ required: true, initial: 0, min: 0 })
|
||||||
|
schema.cost = new fields.NumberField({ ...requiredInteger, required: true, initial: 0, min: 0 })
|
||||||
|
schema.money = new fields.StringField({ required: true, initial: "tinbit", choices: SYSTEM.MONEY })
|
||||||
|
schema.equipped = new fields.BooleanField({ required: true, initial: false })
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["PRISMRPG.Weapon"]
|
||||||
|
|
||||||
|
get weaponCategory() {
|
||||||
|
return game.i18n.localize(CATEGORY[this.weaponType].label)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
|
||||||
|
export default class PrismRPGUtils {
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static async loadCompendiumData(compendium) {
|
||||||
|
const pack = game.packs.get(compendium)
|
||||||
|
return await pack?.getDocuments() ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static async loadCompendium(compendium, filter = item => true) {
|
||||||
|
let compendiumData = await PrismRPGUtils.loadCompendiumData(compendium)
|
||||||
|
return compendiumData.filter(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static pushCombatOptions(html, options) {
|
||||||
|
options.push({ name: "Reset Progression", condition: true, icon: '<i class="fas fa-rotate-right"></i>', callback: target => { game.combat.resetProgression(target.data('combatant-id')); } })
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static setHookListeners() {
|
||||||
|
|
||||||
|
Hooks.on('renderTokenHUD', async (hud, html, token) => {
|
||||||
|
const lossHPButton = await foundry.applications.handlebars.renderTemplate('systems/fvtt-prism-rpg/templates/loss-hp-hud.hbs', {} )
|
||||||
|
$(html).find('div.left').append(lossHPButton);
|
||||||
|
$(html).find('img.prism-hp-loss-hud').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let hpMenu = $(html).find('.hp-loss-wrap')[0]
|
||||||
|
if (hpMenu.classList.contains("hp-loss-hud-disabled")) {
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-disabled');
|
||||||
|
} else {
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(html).find('.loss-hp-hud-click').click((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
let hpLoss = event.currentTarget.dataset.hpValue;
|
||||||
|
if (token) {
|
||||||
|
let tokenFull = canvas.tokens.placeables.find( t => t.id === token._id);
|
||||||
|
console.log(tokenFull, token)
|
||||||
|
let actor = tokenFull.actor;
|
||||||
|
actor.applyDamage(Number(hpLoss));
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[0].classList.add('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[1].classList.add('hp-loss-hud-disabled');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.remove('hp-loss-hud-active');
|
||||||
|
$(html).find('.hp-loss-wrap')[2].classList.add('hp-loss-hud-disabled');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static handleSocketEvent(msg = {}) {
|
||||||
|
console.log(`handleSocketEvent !`, msg)
|
||||||
|
let actor
|
||||||
|
switch (msg.type) {
|
||||||
|
case "rollInitiative":
|
||||||
|
actor = game.actors.get(msg.actorId)
|
||||||
|
actor.system.rollInitiative(msg.combatId, msg.combatantId)
|
||||||
|
break
|
||||||
|
case "rollProgressionDice":
|
||||||
|
actor = game.actors.get(msg.actorId)
|
||||||
|
actor.system.rollProgressionDice(msg.combatId, msg.combatantId, msg.rollProgressionCount)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static registerHandlebarsHelpers() {
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isNull', function (val) {
|
||||||
|
return val == null;
|
||||||
|
});
|
||||||
|
Handlebars.registerHelper('match', function (val, search) {
|
||||||
|
if (val && search) {
|
||||||
|
return val?.match(search);
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('exists', function (val) {
|
||||||
|
return val != null && val !== undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isEmpty', function (list) {
|
||||||
|
if (list) return list.length === 0;
|
||||||
|
else return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('notEmpty', function (list) {
|
||||||
|
return list.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isNegativeOrNull', function (val) {
|
||||||
|
return val <= 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isNegative', function (val) {
|
||||||
|
return val < 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isPositive', function (val) {
|
||||||
|
return val > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('equals', function (val1, val2) {
|
||||||
|
return val1 === val2;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('neq', function (val1, val2) {
|
||||||
|
return val1 !== val2;
|
||||||
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('gt', function (val1, val2) {
|
||||||
|
return val1 > val2;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('lt', function (val1, val2) {
|
||||||
|
return val1 < val2;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('gte', function (val1, val2) {
|
||||||
|
return val1 >= val2;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('lte', function (val1, val2) {
|
||||||
|
return val1 <= val2;
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('and', function (val1, val2) {
|
||||||
|
return val1 && val2;
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('or', function (val1, val2) {
|
||||||
|
return val1 || val2;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('or3', function (val1, val2, val3) {
|
||||||
|
return val1 || val2 || val3;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('for', function (from, to, incr, block) {
|
||||||
|
let accum = '';
|
||||||
|
for (let i = from; i < to; i += incr)
|
||||||
|
accum += block.fn(i);
|
||||||
|
return accum;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('not', function (cond) {
|
||||||
|
return !cond;
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('count', function (list) {
|
||||||
|
return list.length;
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('countKeys', function (obj) {
|
||||||
|
return Object.keys(obj).length;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('isEnabled', function (configKey) {
|
||||||
|
return game.settings.get("bol", configKey);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('split', function (str, separator, keep) {
|
||||||
|
return str.split(separator)[keep];
|
||||||
|
})
|
||||||
|
|
||||||
|
// If you need to add Handlebars helpers, here are a few useful examples:
|
||||||
|
Handlebars.registerHelper('concat', function () {
|
||||||
|
let outStr = '';
|
||||||
|
for (let arg in arguments) {
|
||||||
|
if (typeof arguments[arg] != 'object') {
|
||||||
|
outStr += arguments[arg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outStr;
|
||||||
|
})
|
||||||
|
|
||||||
|
Handlebars.registerHelper('add', function (a, b) {
|
||||||
|
return parseInt(a) + parseInt(b);
|
||||||
|
});
|
||||||
|
Handlebars.registerHelper('mul', function (a, b) {
|
||||||
|
return parseInt(a) * parseInt(b);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('sub', function (a, b) {
|
||||||
|
return parseInt(a) - parseInt(b);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('abbrev2', function (a) {
|
||||||
|
return a.substring(0, 2);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('abbrev3', function (a) {
|
||||||
|
return a.substring(0, 3);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('valueAtIndex', function (arr, idx) {
|
||||||
|
return arr[idx];
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('includesKey', function (items, type, key) {
|
||||||
|
return items.filter(i => i.type === type).map(i => i.system.key).includes(key);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('includes', function (array, val) {
|
||||||
|
return array.includes(val);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('eval', function (expr) {
|
||||||
|
return eval(expr);
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('isOwnerOrGM', function (actor) {
|
||||||
|
console.log("Testing actor", actor.isOwner, game.userId)
|
||||||
|
return actor.isOwner || game.isGM;
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('upperCase', function (text) {
|
||||||
|
if (typeof text !== 'string') return text
|
||||||
|
return text.toUpperCase()
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('upperFirst', function (text) {
|
||||||
|
if (typeof text !== 'string') return text
|
||||||
|
return text.charAt(0).toUpperCase() + text.slice(1)
|
||||||
|
})
|
||||||
|
Handlebars.registerHelper('upperFirstOnly', function (text) {
|
||||||
|
if (typeof text !== 'string') return text
|
||||||
|
return text.charAt(0).toUpperCase()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle v12 removal of this helper
|
||||||
|
Handlebars.registerHelper('select', function (selected, options) {
|
||||||
|
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected));
|
||||||
|
const rgx = new RegExp(' value=[\"\']' + escapedValue + '[\"\']');
|
||||||
|
const html = options.fn(this);
|
||||||
|
return html.replace(rgx, "$& selected");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static getLethargyDice(level) {
|
||||||
|
for (let s of SYSTEM.SPELL_LETHARGY_DICE) {
|
||||||
|
if (Number(level) <= s.maxLevel) {
|
||||||
|
return s.dice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../acorn/bin/acorn
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../errno/cli.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../eslint/bin/eslint.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../eslint-config-prettier/bin/cli.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../@foundryvtt/foundryvtt-cli/fvtt.mjs
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../gulp/bin/gulp.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../image-size/bin/image-size.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../js-yaml/bin/js-yaml.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../less/bin/lessc
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../mime/cli.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../mkdirp/dist/cjs/src/bin.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../needle/bin/needle
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../node-gyp-build/bin.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../node-gyp-build/optional.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../node-gyp-build/build-test.js
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../which/bin/node-which
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../prettier/bin/prettier.cjs
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../resolve/bin/resolve
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
../semver/bin/semver.js
|
||||||
@@ -0,0 +1,589 @@
|
|||||||
|
# CHANGES for `@es-joy/jsdoccomment`
|
||||||
|
|
||||||
|
## 0.46.0
|
||||||
|
|
||||||
|
- chore: update esquery, drop bundling of types, update devDeps
|
||||||
|
|
||||||
|
## 0.45.0
|
||||||
|
|
||||||
|
- feat: get following comment (experimental)
|
||||||
|
|
||||||
|
## 0.44.0
|
||||||
|
|
||||||
|
- feat: add `getNonJsdocComment` for getting non-JSDoc comments above node
|
||||||
|
|
||||||
|
## 0.43.1
|
||||||
|
|
||||||
|
- fix: for `@template` name parsing, ensure (default-)bracketed name is not broken with internal spaces.
|
||||||
|
|
||||||
|
## 0.43.0
|
||||||
|
|
||||||
|
This release brings surgical round trip parsing to generated AST and reconstruction of JSDoc comment blocks via: `parseComment` ->
|
||||||
|
`commentParserToESTree` -> `estreeToString`.
|
||||||
|
|
||||||
|
- feat: new option `spacing` for `commentParserToESTree`; the default is `compact` removing empty description lines.
|
||||||
|
Set to `preserve` to retain empty description lines.
|
||||||
|
- feat: new properties in the `JsdocBlock` generated AST `delimiterLineBreak` and `preterminalLineBreak` that encode
|
||||||
|
any line break after the opening `delimiter` and before the closing `terminal` string. Values are either `\n` or an
|
||||||
|
empty string.
|
||||||
|
|
||||||
|
- chore: update devDeps / switch to Vitest.
|
||||||
|
|
||||||
|
- New [API documentation](https://es-joy.github.io/jsdoccomment/).
|
||||||
|
|
||||||
|
Thanks:
|
||||||
|
- [@typhonrt](https://github.com/typhonrt)
|
||||||
|
|
||||||
|
## 0.42.0
|
||||||
|
|
||||||
|
- feat: expand argument for `parseComment` to accept a comment token string ([@typhonrt](https://github.com/typhonrt))
|
||||||
|
- chore: update devDeps.
|
||||||
|
|
||||||
|
## 0.41.0
|
||||||
|
|
||||||
|
- feat: look above surrounding parenthesis tokens for comment blocks, even if on a higher line than the corresponding AST structure
|
||||||
|
- chore: update comment-parser and devDeps.
|
||||||
|
|
||||||
|
## 0.40.1
|
||||||
|
|
||||||
|
- chore(TS): fix path issue
|
||||||
|
|
||||||
|
## 0.40.0
|
||||||
|
|
||||||
|
- chore: update comment-parser and devDeps.
|
||||||
|
- chore(TS): switch to NodeNext
|
||||||
|
|
||||||
|
## 0.39.4
|
||||||
|
|
||||||
|
- fix: include type exports for full inlineTags (and line) property support on blocks and tags
|
||||||
|
|
||||||
|
## 0.39.3
|
||||||
|
|
||||||
|
- fix: add type details for Node range and settings
|
||||||
|
|
||||||
|
## 0.39.2
|
||||||
|
|
||||||
|
- fix: export additional typedefs from index.js
|
||||||
|
|
||||||
|
## 0.39.1
|
||||||
|
|
||||||
|
- fix: typing export
|
||||||
|
|
||||||
|
## 0.39.0
|
||||||
|
|
||||||
|
- feat: types for test files and emit declaration files
|
||||||
|
- fix(estreeToString): add `JsdodInlineTag` stringify support
|
||||||
|
- refactor: lint
|
||||||
|
- docs: add `JsdocInlineTag` to README
|
||||||
|
- chore: update devDeps.
|
||||||
|
|
||||||
|
## 0.38.0
|
||||||
|
|
||||||
|
- feat: add parsing inline tags (#12); fixes #11
|
||||||
|
|
||||||
|
## 0.37.1
|
||||||
|
|
||||||
|
- chore: support Node 20
|
||||||
|
- chore: update esquery, devDeps.
|
||||||
|
|
||||||
|
## 0.37.0
|
||||||
|
## 0.37.0-pre.0
|
||||||
|
|
||||||
|
- fix: update `jsdoc-type-pratt-parser` (supports bracket indexes)
|
||||||
|
|
||||||
|
## 0.36.1
|
||||||
|
|
||||||
|
- fix(`getReducedASTNode`): stop checking for comment blocks at return
|
||||||
|
statement
|
||||||
|
|
||||||
|
## 0.36.0
|
||||||
|
|
||||||
|
- feat: add `hasPreterminalTagDescription` property
|
||||||
|
- fix: avoid description line properties if tag is present
|
||||||
|
- fix: ensure description and description lines added to terminal multi-line tag
|
||||||
|
|
||||||
|
## 0.35.0
|
||||||
|
|
||||||
|
- feat: add `hasPreterminalDescription` property
|
||||||
|
- fix: allow newline even for 1st line (after 0th)
|
||||||
|
|
||||||
|
## 0.34.0
|
||||||
|
|
||||||
|
- feat: add `descriptionStartLine` and `descriptionEndLine` properties
|
||||||
|
- fix: avoid duplication with 0 line comments
|
||||||
|
- chore: update devDeps.
|
||||||
|
|
||||||
|
## 0.33.4
|
||||||
|
|
||||||
|
- chore: republish as npm seems to have missed the release
|
||||||
|
|
||||||
|
## 0.33.3
|
||||||
|
|
||||||
|
- fix: ensure multi-line `description` includes newline except for
|
||||||
|
initial line descriptions
|
||||||
|
|
||||||
|
## 0.33.2
|
||||||
|
|
||||||
|
- fix: avoid repetition within multi-line descriptions
|
||||||
|
|
||||||
|
## 0.33.1
|
||||||
|
|
||||||
|
- fix: add to default no types: `description`, `example`, `file`,
|
||||||
|
`fileoverview`, `license`, `overview`, `see`, `summary`
|
||||||
|
- fix: add to no names: `file`, `fileoverview, `overview`
|
||||||
|
|
||||||
|
## 0.33.0
|
||||||
|
|
||||||
|
- chore: add Node 19 to `engines` (@RodEsp)
|
||||||
|
- chore: update devDeps. and build file accordingly
|
||||||
|
|
||||||
|
## 0.32.0
|
||||||
|
|
||||||
|
- feat: have comment checking stop at assignment patterns (comments for
|
||||||
|
defaults should not rise to function itself)
|
||||||
|
- chore: bump devDeps.
|
||||||
|
|
||||||
|
## 0.31.0
|
||||||
|
|
||||||
|
- feat: support default values with `@template` per
|
||||||
|
<https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template>
|
||||||
|
|
||||||
|
## 0.30.0
|
||||||
|
|
||||||
|
- chore: bump `jsdoc-type-pratt-parser` and devDeps.
|
||||||
|
|
||||||
|
## 0.29.0
|
||||||
|
|
||||||
|
- fix: update `engines` as per current `getJSDocComment` behavior
|
||||||
|
- chore: update devDeps.
|
||||||
|
|
||||||
|
## 0.28.1
|
||||||
|
|
||||||
|
- fix(`getReducedASTNode`): token checking
|
||||||
|
- build: add Node 18 support (@WikiRik)
|
||||||
|
|
||||||
|
## 0.28.0
|
||||||
|
|
||||||
|
- chore: bump `engines` to support Node 18
|
||||||
|
|
||||||
|
## 0.27.0
|
||||||
|
|
||||||
|
- chore: bump `jsdoc-type-pratt-parser` and devDeps.
|
||||||
|
|
||||||
|
## 0.26.1
|
||||||
|
|
||||||
|
- fix(`estreeToString`): ensure `typeLines` may be picked up
|
||||||
|
|
||||||
|
## 0.26.0
|
||||||
|
|
||||||
|
- feat(`getJSDocComment`): allow function to detect comments just preceding a
|
||||||
|
parenthesized expression (these have no special AST but their tokens
|
||||||
|
have to be overpassed)
|
||||||
|
|
||||||
|
## 0.25.0
|
||||||
|
|
||||||
|
- feat(`parseComment`): properly support whitespace
|
||||||
|
- fix(`estreeToString`): carriage return placement for ending of JSDoc block
|
||||||
|
- fix(`commentParserToESTree`): avoid adding initial space before a tag if on
|
||||||
|
a single line
|
||||||
|
- test: make tests more accurate to jsdoc semantically
|
||||||
|
|
||||||
|
## 0.24.0
|
||||||
|
|
||||||
|
- feat(`estreeToString`): support stringification of `parsedType` but with
|
||||||
|
a new `preferRawType` option allowing the old behavior of using `rawType`
|
||||||
|
|
||||||
|
## 0.23.6
|
||||||
|
|
||||||
|
- fix(`commentParserToESTree`): ensure `postType` added after multi-line type
|
||||||
|
- fix(`estreeToString`): ensure `JsdocTypeLine` stringified with `initial` and
|
||||||
|
that they are joined together with newlines
|
||||||
|
|
||||||
|
## 0.23.5
|
||||||
|
|
||||||
|
- fix(`commentParserToESTree`): avoid duplicating tag names
|
||||||
|
|
||||||
|
## 0.23.4
|
||||||
|
|
||||||
|
- fix(`estreeToString`): add `delimiter`, etc. if adding `JsdocDescriptionLine`
|
||||||
|
for `JsdocBlock`
|
||||||
|
- fix(`estreeToString`): add line break when tags are present (unless already
|
||||||
|
ending in newline)
|
||||||
|
|
||||||
|
## 0.23.3
|
||||||
|
|
||||||
|
- fix(`estreeToString`): handle multi-line block descriptions followed by
|
||||||
|
tags with line break
|
||||||
|
|
||||||
|
## 0.23.2
|
||||||
|
|
||||||
|
- fix: ensure JsdocBlock stringifier has any initial whitespace on end line
|
||||||
|
|
||||||
|
## 0.23.1
|
||||||
|
|
||||||
|
- docs(README): update
|
||||||
|
|
||||||
|
## 0.23.0
|
||||||
|
|
||||||
|
- BREAKING CHANGE(`commentParserToESTree`): rename `start` and `end` to
|
||||||
|
`initial` and `terminal` to avoid any conflicts with Acorn-style parsers
|
||||||
|
- feat: add `initial` and `terminal` on `JsdocBlock`
|
||||||
|
|
||||||
|
## 0.22.2
|
||||||
|
|
||||||
|
- fix: preserve type tokens
|
||||||
|
- perf: cache tokenizers
|
||||||
|
|
||||||
|
## 0.22.1
|
||||||
|
|
||||||
|
- fix: ensure `getJSDocComment` does not treat block comments as JSDoc unless
|
||||||
|
their first asterisk is followed by whitespace
|
||||||
|
|
||||||
|
## 0.22.0
|
||||||
|
|
||||||
|
- fix: update dep. `jsdoc-type-pratt-parser`
|
||||||
|
- chore: update `comment-parser` and simplify as possible
|
||||||
|
|
||||||
|
## 0.21.2
|
||||||
|
|
||||||
|
- fix: only throw if the raw type is not empty
|
||||||
|
|
||||||
|
## 0.21.1
|
||||||
|
|
||||||
|
- fix: provide clearer error message for `throwOnTypeParsingErrors`
|
||||||
|
|
||||||
|
## 0.21.0
|
||||||
|
|
||||||
|
- feat: add `throwOnTypeParsingErrors` to receive run-time type parsing errors
|
||||||
|
for `parsedType`
|
||||||
|
- chore: update jsdoc-type-pratt-parser and devDeps.; also lints
|
||||||
|
|
||||||
|
## 0.20.1
|
||||||
|
|
||||||
|
- fix: resume catching bad parsed type (at least until
|
||||||
|
`jsdoc-type-pratt-parser` may support all expected types)
|
||||||
|
|
||||||
|
## 0.20.0
|
||||||
|
|
||||||
|
- feat: add estree stringifer
|
||||||
|
- fix: properly supports `name`/`postName` for multi-line type
|
||||||
|
- fix: allow pratt parser to fail (unless empty)
|
||||||
|
- fix: don't add tag postDelimiter when on 0 description line
|
||||||
|
- fix: avoid adding extra line when only name and no succeeding description
|
||||||
|
- docs: clarify re: `kind`
|
||||||
|
- test: add `parsedType` with correct mode; add tests
|
||||||
|
- chore: updates jsdoc-type-pratt-parser
|
||||||
|
- chore: updates devDeps.
|
||||||
|
|
||||||
|
## 0.19.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- feat: treat `@kind` as having no name
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- docs: jsdoc
|
||||||
|
- test: begin checking `jsdoccomment`
|
||||||
|
- test: adds lcov reporter and open script for it
|
||||||
|
- chore: update devDeps.
|
||||||
|
|
||||||
|
## 0.18.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- feat: add non-visitable `endLine` property (so can detect line number
|
||||||
|
when no description present)
|
||||||
|
- feat: supply `indent` default for `parseComment`
|
||||||
|
- fix: ensure `postName` gets a space for `@template` with a description
|
||||||
|
- fix: converting JSDoc comment with tag on same line as end (e.g., single
|
||||||
|
line) to AST
|
||||||
|
- chore: update `jsdoc-type-pratt-parser`
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- docs: add jsdoc blocks internally
|
||||||
|
- chore: update devDeps.
|
||||||
|
- test: avoid need for `expect`
|
||||||
|
- test: complete coverage for `commentHandler`, `parseComment` tests
|
||||||
|
|
||||||
|
## 0.17.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Enhancement: Re-export `jsdoc-type-pratt-parser`
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.2.1
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.16.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.2.0
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.15.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.1.0
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.14.2
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Fix: Find comments previous to parentheses (used commonly in TypeScript)
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.14.1
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.0.2
|
||||||
|
|
||||||
|
## 0.14.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.0.1
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.13.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `comment-parser` to 1.3.0
|
||||||
|
- Fix: Allow comment on `ExportDefaultDeclaration`
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: `jsdoc-type-pratt-parser` to 2.0.0
|
||||||
|
- Enhancement: Support Node 17 (@timgates42)
|
||||||
|
- Docs: Typo (@timgates42)
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- Linting: As per latest ash-nazg
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.11.0
|
||||||
|
|
||||||
|
- Update: For `@typescript/eslint-parser@5`, add `PropertyDefinition`
|
||||||
|
|
||||||
|
## 0.10.8
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- npm: Liberalize `engines` as per `comment-parser` change
|
||||||
|
- npm: Bump `comment-parser`
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- Linting: As per latest ash-nazg
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.10.7
|
||||||
|
|
||||||
|
- npm: Update comment-parser with CJS fix and re-exports
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.10.6
|
||||||
|
|
||||||
|
- Fix: Ensure copying latest build of `comment-parser`'s ESM utils
|
||||||
|
|
||||||
|
## 0.10.5
|
||||||
|
|
||||||
|
- npm: Bump fixed `jsdoc-type-pratt-parser` and devDeps.
|
||||||
|
|
||||||
|
## 0.10.4
|
||||||
|
|
||||||
|
- Fix: Bundle `comment-parser` nested imports so that IDEs (like Atom)
|
||||||
|
bundling older Node versions can still work. Still mirroring the
|
||||||
|
stricter `comment-parser` `engines` for now, however.
|
||||||
|
|
||||||
|
## 0.10.3
|
||||||
|
|
||||||
|
- npm: Avoid exporting nested subpaths for sake of older Node versions
|
||||||
|
|
||||||
|
## 0.10.2
|
||||||
|
|
||||||
|
- npm: Specify exact supported range: `^12.20 || ^14.14.0 || ^16`
|
||||||
|
|
||||||
|
## 0.10.1
|
||||||
|
|
||||||
|
- npm: Apply patch version of `comment-parser`
|
||||||
|
|
||||||
|
## 0.10.0
|
||||||
|
|
||||||
|
- npm: Point to stable `comment-parser`
|
||||||
|
|
||||||
|
## 0.9.0-alpha.6
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Update: For `comment-parser` update, add `lineEnd`
|
||||||
|
|
||||||
|
## 0.9.0-alpha.5
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- npm: Bump `comment-parser` (for true ESM)
|
||||||
|
- Update: Remove extensions for packages for native ESM in `comment-parser` fix
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.9.0-alpha.4
|
||||||
|
|
||||||
|
- Docs: Update repo info in `package.json`
|
||||||
|
|
||||||
|
## 0.9.0-alpha.3
|
||||||
|
|
||||||
|
- Fix: Due to `comment-parser` still needing changes, revert for now to alpha.1
|
||||||
|
|
||||||
|
## 0.9.0-alpha.2
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- npm: Bump `comment-parser` (for true ESM)
|
||||||
|
- Update: Remove extensions for packages for native ESM in `comment-parser` fix
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.9.0-alpha.1
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- Breaking change: Indicate minimum for `engines` as Node >= 12
|
||||||
|
- npm: Bump `comment-parser`
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Lint cjs files
|
||||||
|
- npm: Fix eslint script
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
|
### User-impacting
|
||||||
|
|
||||||
|
- npm: Update `jsdoc-type-pratt-parser` (prerelease to stable patch)
|
||||||
|
|
||||||
|
### Dev-impacting
|
||||||
|
|
||||||
|
- npm: Update devDeps.
|
||||||
|
|
||||||
|
## 0.8.0-alpha.2
|
||||||
|
|
||||||
|
- Fix: Avoid erring with missing `typeLines`
|
||||||
|
|
||||||
|
## 0.8.0-alpha.1
|
||||||
|
|
||||||
|
- Breaking change: Export globally as `JsdocComment`
|
||||||
|
- Breaking change: Change `JSDoc` prefixes of all node types to `Jsdoc`
|
||||||
|
- Breaking change: Drop `jsdoctypeparserToESTree`
|
||||||
|
- Breaking enhancement: Switch to `jsdoc-type-pratt-parser` (toward greater
|
||||||
|
TypeScript expressivity and compatibility/support with catharsis)
|
||||||
|
- Enhancement: Export `jsdocTypeVisitorKeys` (from `jsdoc-type-pratt-parser`)
|
||||||
|
|
||||||
|
## 0.7.2
|
||||||
|
|
||||||
|
- Fix: Add `@description` to `noNames`
|
||||||
|
|
||||||
|
## 0.7.1
|
||||||
|
|
||||||
|
- Fix: Add `@summary` to `noNames`
|
||||||
|
|
||||||
|
## 0.7.0
|
||||||
|
|
||||||
|
- Enhancement: Allow specifying `noNames` and `noTypes` on `parseComment`
|
||||||
|
to override (or add to) tags which should have no names or types.
|
||||||
|
- Enhancement: Export `hasSeeWithLink` utility and `defaultNoTypes` and
|
||||||
|
`defaultNoNames`.
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
- Change `comment-parser` `tag` AST to avoid initial `@`
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
- Fix: Avoid setting `variation` name (just the description) (including in
|
||||||
|
dist)
|
||||||
|
- npm: Add `prepublishOnly` script
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
- Fix: Avoid setting `variation` name (just the description)
|
||||||
|
|
||||||
|
## 0.4.4
|
||||||
|
|
||||||
|
- Fix: Avoid setting `name` and `description` for simple `@template SomeName`
|
||||||
|
|
||||||
|
## 0.4.3
|
||||||
|
|
||||||
|
- npm: Ignores Github file
|
||||||
|
|
||||||
|
## 0.4.2
|
||||||
|
|
||||||
|
- Fix: Ensure replacement of camel-casing (used in `jsdoctypeparser` nodes and
|
||||||
|
visitor keys is global. The practical effect is that
|
||||||
|
`JSDocTypeNamed_parameter` -> `JSDocTypeNamedParameter`,
|
||||||
|
`JSDocTypeRecord_entry` -> `JSDocTypeRecordEntry`
|
||||||
|
`JSDocTypeNot_nullable` -> `JSDocTypeNotNullable`
|
||||||
|
`JSDocTypeInner_member` -> `JSDocTypeInnerMember`
|
||||||
|
`JSDocTypeInstance_member` -> `JSDocTypeInstanceMember`
|
||||||
|
`JSDocTypeString_value` -> `JSDocTypeStringValue`
|
||||||
|
`JSDocTypeNumber_value` -> `JSDocTypeNumberValue`
|
||||||
|
`JSDocTypeFile_path` -> `JSDocTypeFilePath`
|
||||||
|
`JSDocTypeType_query` -> `JSDocTypeTypeQuery`
|
||||||
|
`JSDocTypeKey_query` -> `JSDocTypeKeyQuery`
|
||||||
|
- Fix: Add missing `JSDocTypeLine` to visitor keys
|
||||||
|
- Docs: Explain AST structure/differences
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
- Docs: Indicate available methods with brief summary on README
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
- Enhancement: Expose `parseComment` and `getTokenizers`.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
- Enhancement: Expose `toCamelCase` as new method rather than within a
|
||||||
|
utility file.
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
- Enhancement: Exposes new methods: `commentHandler`,
|
||||||
|
`commentParserToESTree`, `jsdocVisitorKeys`, `jsdoctypeparserToESTree`,
|
||||||
|
`jsdocTypeVisitorKeys`,
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
- Build: Add Babel to work with earlier Node
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
- Initial version
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright JS Foundation and other contributors, https://js.foundation
|
||||||
|
Copyright (c) 2021 Brett Zamir
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# @es-joy/jsdoccomment
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@es-joy/jsdoccomment)
|
||||||
|
[](https://github.com/es-joy/jsdoccomment/blob/main/LICENSE-MIT.txt)
|
||||||
|
[](#)
|
||||||
|
[](https://es-joy.github.io/jsdoccomment/)
|
||||||
|
|
||||||
|
|
||||||
|
This project aims to preserve and expand upon the
|
||||||
|
`SourceCode#getJSDocComment` functionality of the deprecated ESLint method.
|
||||||
|
|
||||||
|
It also exports a number of functions currently for working with JSDoc:
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `parseComment`
|
||||||
|
|
||||||
|
For parsing `comment-parser` in a JSDoc-specific manner.
|
||||||
|
Might wish to have tags with or without tags, etc. derived from a split off
|
||||||
|
JSON file.
|
||||||
|
|
||||||
|
### `commentParserToESTree`
|
||||||
|
|
||||||
|
Converts [comment-parser](https://github.com/syavorsky/comment-parser)
|
||||||
|
AST to ESTree/ESLint/Babel friendly AST. See the "ESLint AST..." section below.
|
||||||
|
|
||||||
|
### `estreeToString`
|
||||||
|
|
||||||
|
Stringifies. In addition to the node argument, it accepts an optional second
|
||||||
|
options object with a single `preferRawType` key. If you don't need to modify
|
||||||
|
JSDoc type AST, you might wish to set this to `true` to get the benefits of
|
||||||
|
preserving the raw form, but for AST-based stringification of JSDoc types,
|
||||||
|
keep it `false` (the default).
|
||||||
|
|
||||||
|
### `jsdocVisitorKeys`
|
||||||
|
|
||||||
|
The [VisitorKeys](https://github.com/eslint/eslint-visitor-keys)
|
||||||
|
for `JsdocBlock`, `JsdocDescriptionLine`, and `JsdocTag`. More likely to be
|
||||||
|
subject to change or dropped in favor of another type parser.
|
||||||
|
|
||||||
|
### `jsdocTypeVisitorKeys`
|
||||||
|
|
||||||
|
Just a re-export of [VisitorKeys](https://github.com/eslint/eslint-visitor-keys)
|
||||||
|
from [`jsdoc-type-pratt-parser`](https://github.com/simonseyock/jsdoc-type-pratt-parser/).
|
||||||
|
|
||||||
|
### `getDefaultTagStructureForMode`
|
||||||
|
|
||||||
|
Provides info on JSDoc tags:
|
||||||
|
|
||||||
|
- `nameContents` ('namepath-referencing'|'namepath-defining'|
|
||||||
|
'dual-namepath-referencing'|false) - Whether and how a name is allowed
|
||||||
|
following any type. Tags without a proper name (value `false`) may still
|
||||||
|
have a description (which can appear like a name); `descriptionAllowed`
|
||||||
|
in such cases would be `true`.
|
||||||
|
The presence of a truthy `nameContents` value is therefore only intended
|
||||||
|
to signify whether separate parsing should occur for a name vs. a
|
||||||
|
description, and what its nature should be.
|
||||||
|
- `nameRequired` (boolean) - Whether a name must be present following any type.
|
||||||
|
- `descriptionAllowed` (boolean) - Whether a description (following any name)
|
||||||
|
is allowed.
|
||||||
|
- `typeAllowed` (boolean) - Whether the tag accepts a curly bracketed portion.
|
||||||
|
Even without a type, a tag may still have a name and/or description.
|
||||||
|
- `typeRequired` (boolean) - Whether a curly bracketed type must be present.
|
||||||
|
- `typeOrNameRequired` (boolean) - Whether either a curly bracketed type is
|
||||||
|
required or a name, but not necessarily both.
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
Also currently exports these utilities:
|
||||||
|
|
||||||
|
- `getTokenizers` - Used with `parseComment` (its main core).
|
||||||
|
- `hasSeeWithLink` - A utility to detect if a tag is `@see` and has a `@link`.
|
||||||
|
- `commentHandler` - Used by `eslint-plugin-jsdoc`.
|
||||||
|
- `commentParserToESTree`- Converts [comment-parser](https://github.com/syavorsky/comment-parser)
|
||||||
|
AST to ESTree/ESLint/Babel friendly AST.
|
||||||
|
- `jsdocVisitorKeys` - The [VisitorKeys](https://github.com/eslint/eslint-visitor-keys)
|
||||||
|
for `JSDocBlock`, `JSDocDescriptionLine`, and `JSDocTag`.
|
||||||
|
- `jsdocTypeVisitorKeys` - [VisitorKeys](https://github.com/eslint/eslint-visitor-keys)
|
||||||
|
for `jsdoc-type-pratt-parser`.
|
||||||
|
- `defaultNoTypes` = The tags which allow no types by default:
|
||||||
|
`default`, `defaultvalue`, `description`, `example`, `file`,
|
||||||
|
`fileoverview`, `license`, `overview`, `see`, `summary`
|
||||||
|
- `defaultNoNames` - The tags which allow no names by default:
|
||||||
|
`access`, `author`, `default`, `defaultvalue`, `description`, `example`,
|
||||||
|
`exception`, `file`, `fileoverview`, `kind`, `license`, `overview`,
|
||||||
|
`return`, `returns`, `since`, `summary`, `throws`, `version`, `variation`
|
||||||
|
|
||||||
|
## ESLint AST produced for `comment-parser` nodes (`JsdocBlock`, `JsdocTag`, and `JsdocDescriptionLine`)
|
||||||
|
|
||||||
|
Note: Although not added in this package, `@es-joy/jsdoc-eslint-parser` adds
|
||||||
|
a `jsdoc` property to other ES nodes (using this project's `getJSDocComment`
|
||||||
|
to determine the specific comment-block that will be attached as AST).
|
||||||
|
|
||||||
|
### `JsdocBlock`
|
||||||
|
|
||||||
|
Has the following visitable properties:
|
||||||
|
|
||||||
|
1. `descriptionLines` (an array of `JsdocDescriptionLine` for multiline
|
||||||
|
descriptions).
|
||||||
|
2. `tags` (an array of `JsdocTag`; see below)
|
||||||
|
3. `inlineTags` (an array of `JsdocInlineTag`; see below)
|
||||||
|
|
||||||
|
Has the following custom non-visitable property:
|
||||||
|
|
||||||
|
1. `delimiterLineBreak` - A string containing any line break after `delimiter`.
|
||||||
|
2. `lastDescriptionLine` - A number
|
||||||
|
3. `endLine` - A number representing the line number with `end`/`terminal`
|
||||||
|
4. `descriptionStartLine` - A 0+ number indicating the line where any
|
||||||
|
description begins
|
||||||
|
5. `descriptionEndLine` - A 0+ number indicating the line where the description
|
||||||
|
ends
|
||||||
|
6. `hasPreterminalDescription` - Set to 0 or 1. On if has a block description
|
||||||
|
on the same line as the terminal `*/`.
|
||||||
|
7. `hasPreterminalTagDescription` - Set to 0 or 1. On if has a tag description
|
||||||
|
on the same line as the terminal `*/`.
|
||||||
|
8. `preterminalLineBreak` - A string containing any line break before `terminal`.
|
||||||
|
|
||||||
|
May also have the following non-visitable properties from `comment-parser`:
|
||||||
|
|
||||||
|
1. `description` - Same as `descriptionLines` but as a string with newlines.
|
||||||
|
2. `delimiter`
|
||||||
|
3. `postDelimiter`
|
||||||
|
4. `lineEnd`
|
||||||
|
5. `initial` (from `start`)
|
||||||
|
6. `terminal` (from `end`)
|
||||||
|
|
||||||
|
### `JsdocTag`
|
||||||
|
|
||||||
|
Has the following visitable properties:
|
||||||
|
|
||||||
|
1. `parsedType` (the `jsdoc-type-pratt-parser` AST representation of the tag's
|
||||||
|
type (see the `jsdoc-type-pratt-parser` section below)).
|
||||||
|
2. `typeLines` (an array of `JsdocTypeLine` for multiline type strings)
|
||||||
|
3. `descriptionLines` (an array of `JsdocDescriptionLine` for multiline
|
||||||
|
descriptions)
|
||||||
|
4. `inlineTags` (an array of `JsdocInlineTag`)
|
||||||
|
|
||||||
|
May also have the following non-visitable properties from `comment-parser`
|
||||||
|
(note that all are included from `comment-parser` except `end` as that is only
|
||||||
|
for JSDoc blocks and note that `type` is renamed to `rawType` and `start` to
|
||||||
|
`initial`):
|
||||||
|
|
||||||
|
1. `description` - Same as `descriptionLines` but as a string with newlines.
|
||||||
|
2. `rawType` - `comment-parser` has this named as `type`, but because of a
|
||||||
|
conflict with ESTree using `type` for Node type, we renamed it to
|
||||||
|
`rawType`. It is otherwise the same as in `comment-parser`, i.e., a string
|
||||||
|
with newlines, though with the initial `{` and final `}` stripped out.
|
||||||
|
See `typeLines` for the array version of this property.
|
||||||
|
3. `initial` - Renamed from `start` to avoid potential conflicts with
|
||||||
|
Acorn-style parser processing tools
|
||||||
|
4. `delimiter`
|
||||||
|
5. `postDelimiter`
|
||||||
|
6. `tag` (this does differ from `comment-parser` now in terms of our stripping
|
||||||
|
the initial `@`)
|
||||||
|
7. `postTag`
|
||||||
|
8. `name`
|
||||||
|
9. `postName`
|
||||||
|
10. `postType`
|
||||||
|
|
||||||
|
### `JsdocDescriptionLine`
|
||||||
|
|
||||||
|
No visitable properties.
|
||||||
|
|
||||||
|
May also have the following non-visitable properties from `comment-parser`:
|
||||||
|
|
||||||
|
1. `delimiter`
|
||||||
|
2. `postDelimiter`
|
||||||
|
3. `initial` (from `start`)
|
||||||
|
4. `description`
|
||||||
|
|
||||||
|
### `JsdocTypeLine`
|
||||||
|
|
||||||
|
No visitable properties.
|
||||||
|
|
||||||
|
May also have the following non-visitable properties from `comment-parser`:
|
||||||
|
|
||||||
|
1. `delimiter`
|
||||||
|
2. `postDelimiter`
|
||||||
|
3. `initial` (from `start`)
|
||||||
|
4. `rawType` - Renamed from `comment-parser` to avoid a conflict. See
|
||||||
|
explanation under `JsdocTag`
|
||||||
|
|
||||||
|
### `JsdocInlineTag`
|
||||||
|
|
||||||
|
No visitable properties.
|
||||||
|
|
||||||
|
Has the following non-visitable properties:
|
||||||
|
|
||||||
|
1. `format`: 'pipe' | 'plain' | 'prefix' | 'space'. These follow the styles of [link](https://jsdoc.app/tags-inline-link.html) or [tutorial](https://jsdoc.app/tags-inline-tutorial.html).
|
||||||
|
1. `pipe`: `{@link namepathOrURL|link text}`
|
||||||
|
2. `plain`: `{@link namepathOrURL}`
|
||||||
|
3. `prefix`: `[link text]{@link namepathOrURL}`
|
||||||
|
4. `space`: `{@link namepathOrURL link text (after the first space)}`
|
||||||
|
2. `namepathOrURL`: string
|
||||||
|
3. `tag`: string. The standard allows `tutorial` or `link`
|
||||||
|
4. `text`: string
|
||||||
|
|
||||||
|
## ESLint AST produced for `jsdoc-type-pratt-parser`
|
||||||
|
|
||||||
|
The AST, including `type`, remains as is from [jsdoc-type-pratt-parser](https://github.com/simonseyock/jsdoc-type-pratt-parser/).
|
||||||
|
|
||||||
|
The type will always begin with a `JsdocType` prefix added, along with a
|
||||||
|
camel-cased type name, e.g., `JsdocTypeUnion`.
|
||||||
|
|
||||||
|
The `jsdoc-type-pratt-parser` visitor keys are also preserved without change.
|
||||||
|
|
||||||
|
You can get a sense of the structure of these types using the parser's
|
||||||
|
[tester](https://jsdoc-type-pratt-parser.github.io/jsdoc-type-pratt-parser/).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm i @es-joy/jsdoccomment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
The changelog can be found on the [CHANGES.md](https://github.com/es-joy/jsdoccomment/blob/main/CHANGES.md).
|
||||||
|
<!--## Contributing
|
||||||
|
|
||||||
|
Everyone is welcome to contribute. Please take a moment to review the [contributing guidelines](CONTRIBUTING.md).
|
||||||
|
-->
|
||||||
|
## Authors and license
|
||||||
|
|
||||||
|
[Brett Zamir](http://brett-zamir.me/) and
|
||||||
|
[contributors](https://github.com/es-joy/jsdoccomment/graphs/contributors).
|
||||||
|
|
||||||
|
MIT License, see the included [LICENSE-MIT.txt](https://github.com/es-joy/jsdoccomment/blob/main/LICENSE-MIT.txt) file.
|
||||||
|
|
||||||
|
## To-dos
|
||||||
|
|
||||||
|
1. Get complete code coverage
|
||||||
|
1. Given that `esquery` expects a `right` property to search for `>` (the
|
||||||
|
child selector), we should perhaps insist, for example, that params are
|
||||||
|
the child property for `JsdocBlock` or such. Where `:has()` is currently
|
||||||
|
needed, one could thus instead just use `>`.
|
||||||
|
1. Might add `trailing` for `JsdocBlock` to know whether it is followed by a
|
||||||
|
line break or what not; `comment-parser` does not provide, however
|
||||||
|
1. Fix and properly utilize `indent` argument (challenging for
|
||||||
|
`eslint-plugin-jsdoc` but needed for `jsdoc-eslint-parser` stringifiers
|
||||||
|
to be more faithful); should also then use the proposed `trailing` as well
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
import * as comment_parser from 'comment-parser';
|
||||||
|
import * as jsdoc_type_pratt_parser from 'jsdoc-type-pratt-parser';
|
||||||
|
export * from 'jsdoc-type-pratt-parser';
|
||||||
|
export { visitorKeys as jsdocTypeVisitorKeys } from 'jsdoc-type-pratt-parser';
|
||||||
|
import * as _typescript_eslint_types from '@typescript-eslint/types';
|
||||||
|
import * as estree from 'estree';
|
||||||
|
import * as eslint from 'eslint';
|
||||||
|
|
||||||
|
type JsdocTypeLine = {
|
||||||
|
delimiter: string;
|
||||||
|
postDelimiter: string;
|
||||||
|
rawType: string;
|
||||||
|
initial: string;
|
||||||
|
type: 'JsdocTypeLine';
|
||||||
|
};
|
||||||
|
type JsdocDescriptionLine = {
|
||||||
|
delimiter: string;
|
||||||
|
description: string;
|
||||||
|
postDelimiter: string;
|
||||||
|
initial: string;
|
||||||
|
type: 'JsdocDescriptionLine';
|
||||||
|
};
|
||||||
|
type JsdocInlineTagNoType = {
|
||||||
|
format: 'pipe' | 'plain' | 'prefix' | 'space';
|
||||||
|
namepathOrURL: string;
|
||||||
|
tag: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
type JsdocInlineTag = JsdocInlineTagNoType & {
|
||||||
|
type: 'JsdocInlineTag';
|
||||||
|
};
|
||||||
|
type JsdocTag = {
|
||||||
|
delimiter: string;
|
||||||
|
description: string;
|
||||||
|
descriptionLines: JsdocDescriptionLine[];
|
||||||
|
initial: string;
|
||||||
|
inlineTags: JsdocInlineTag[];
|
||||||
|
name: string;
|
||||||
|
postDelimiter: string;
|
||||||
|
postName: string;
|
||||||
|
postTag: string;
|
||||||
|
postType: string;
|
||||||
|
rawType: string;
|
||||||
|
parsedType: jsdoc_type_pratt_parser.RootResult | null;
|
||||||
|
tag: string;
|
||||||
|
type: 'JsdocTag';
|
||||||
|
typeLines: JsdocTypeLine[];
|
||||||
|
};
|
||||||
|
type Integer = number;
|
||||||
|
type JsdocBlock = {
|
||||||
|
delimiter: string;
|
||||||
|
delimiterLineBreak: string;
|
||||||
|
description: string;
|
||||||
|
descriptionEndLine?: Integer;
|
||||||
|
descriptionLines: JsdocDescriptionLine[];
|
||||||
|
descriptionStartLine?: Integer;
|
||||||
|
hasPreterminalDescription: 0 | 1;
|
||||||
|
hasPreterminalTagDescription?: 1;
|
||||||
|
initial: string;
|
||||||
|
inlineTags: JsdocInlineTag[];
|
||||||
|
lastDescriptionLine?: Integer;
|
||||||
|
endLine: Integer;
|
||||||
|
lineEnd: string;
|
||||||
|
postDelimiter: string;
|
||||||
|
tags: JsdocTag[];
|
||||||
|
terminal: string;
|
||||||
|
preterminalLineBreak: string;
|
||||||
|
type: 'JsdocBlock';
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Converts comment parser AST to ESTree format.
|
||||||
|
* @param {import('.').JsdocBlockWithInline} jsdoc
|
||||||
|
* @param {import('jsdoc-type-pratt-parser').ParseMode} mode
|
||||||
|
* @param {object} opts
|
||||||
|
* @param {'compact'|'preserve'} [opts.spacing] By default, empty lines are
|
||||||
|
* compacted; set to 'preserve' to preserve empty comment lines.
|
||||||
|
* @param {boolean} [opts.throwOnTypeParsingErrors]
|
||||||
|
* @returns {JsdocBlock}
|
||||||
|
*/
|
||||||
|
declare function commentParserToESTree(
|
||||||
|
jsdoc: JsdocBlockWithInline,
|
||||||
|
mode: jsdoc_type_pratt_parser.ParseMode,
|
||||||
|
{
|
||||||
|
spacing,
|
||||||
|
throwOnTypeParsingErrors,
|
||||||
|
}?: {
|
||||||
|
spacing?: 'compact' | 'preserve';
|
||||||
|
throwOnTypeParsingErrors?: boolean;
|
||||||
|
},
|
||||||
|
): JsdocBlock;
|
||||||
|
declare namespace jsdocVisitorKeys {
|
||||||
|
let JsdocBlock: string[];
|
||||||
|
let JsdocDescriptionLine: any[];
|
||||||
|
let JsdocTypeLine: any[];
|
||||||
|
let JsdocTag: string[];
|
||||||
|
let JsdocInlineTag: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{[name: string]: any}} settings
|
||||||
|
* @returns {import('.').CommentHandler}
|
||||||
|
*/
|
||||||
|
declare function commentHandler(settings: { [name: string]: any }): CommentHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo convert for use by escodegen (until may be patched to support
|
||||||
|
* custom entries?).
|
||||||
|
* @param {import('./commentParserToESTree').JsdocBlock|
|
||||||
|
* import('./commentParserToESTree').JsdocDescriptionLine|
|
||||||
|
* import('./commentParserToESTree').JsdocTypeLine|
|
||||||
|
* import('./commentParserToESTree').JsdocTag|
|
||||||
|
* import('./commentParserToESTree').JsdocInlineTag|
|
||||||
|
* import('jsdoc-type-pratt-parser').RootResult
|
||||||
|
* } node
|
||||||
|
* @param {import('.').ESTreeToStringOptions} opts
|
||||||
|
* @throws {Error}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
declare function estreeToString(
|
||||||
|
node:
|
||||||
|
| JsdocBlock
|
||||||
|
| JsdocDescriptionLine
|
||||||
|
| JsdocTypeLine
|
||||||
|
| JsdocTag
|
||||||
|
| JsdocInlineTag
|
||||||
|
| jsdoc_type_pratt_parser.RootResult,
|
||||||
|
opts?: ESTreeToStringOptions,
|
||||||
|
): string;
|
||||||
|
|
||||||
|
type Token =
|
||||||
|
| eslint.AST.Token
|
||||||
|
| estree.Comment
|
||||||
|
| {
|
||||||
|
type: eslint.AST.TokenType | 'Line' | 'Block' | 'Shebang';
|
||||||
|
range: [number, number];
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
type ESLintOrTSNode = eslint.Rule.Node | _typescript_eslint_types.TSESTree.Node;
|
||||||
|
type int = number;
|
||||||
|
/**
|
||||||
|
* Reduces the provided node to the appropriate node for evaluating
|
||||||
|
* JSDoc comment status.
|
||||||
|
*
|
||||||
|
* @param {ESLintOrTSNode} node An AST node.
|
||||||
|
* @param {import('eslint').SourceCode} sourceCode The ESLint SourceCode.
|
||||||
|
* @returns {ESLintOrTSNode} The AST node that
|
||||||
|
* can be evaluated for appropriate JSDoc comments.
|
||||||
|
*/
|
||||||
|
declare function getReducedASTNode(node: ESLintOrTSNode, sourceCode: eslint.SourceCode): ESLintOrTSNode;
|
||||||
|
/**
|
||||||
|
* Retrieves the JSDoc comment for a given node.
|
||||||
|
*
|
||||||
|
* @param {import('eslint').SourceCode} sourceCode The ESLint SourceCode
|
||||||
|
* @param {import('eslint').Rule.Node} node The AST node to get
|
||||||
|
* the comment for.
|
||||||
|
* @param {{maxLines: int, minLines: int, [name: string]: any}} settings The
|
||||||
|
* settings in context
|
||||||
|
* @returns {Token|null} The Block comment
|
||||||
|
* token containing the JSDoc comment for the given node or
|
||||||
|
* null if not found.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
declare function getJSDocComment(
|
||||||
|
sourceCode: eslint.SourceCode,
|
||||||
|
node: eslint.Rule.Node,
|
||||||
|
settings: {
|
||||||
|
maxLines: int;
|
||||||
|
minLines: int;
|
||||||
|
[name: string]: any;
|
||||||
|
},
|
||||||
|
): Token | null;
|
||||||
|
/**
|
||||||
|
* Retrieves the comment preceding a given node.
|
||||||
|
*
|
||||||
|
* @param {import('eslint').SourceCode} sourceCode The ESLint SourceCode
|
||||||
|
* @param {ESLintOrTSNode} node The AST node to get
|
||||||
|
* the comment for.
|
||||||
|
* @param {{maxLines: int, minLines: int, [name: string]: any}} settings The
|
||||||
|
* settings in context
|
||||||
|
* @returns {Token|null} The Block comment
|
||||||
|
* token containing the JSDoc comment for the given node or
|
||||||
|
* null if not found.
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
declare function getNonJsdocComment(
|
||||||
|
sourceCode: eslint.SourceCode,
|
||||||
|
node: ESLintOrTSNode,
|
||||||
|
settings: {
|
||||||
|
maxLines: int;
|
||||||
|
minLines: int;
|
||||||
|
[name: string]: any;
|
||||||
|
},
|
||||||
|
): Token | null;
|
||||||
|
/**
|
||||||
|
* @param {(ESLintOrTSNode|import('estree').Comment) & {
|
||||||
|
* declaration?: any,
|
||||||
|
* decorators?: any[],
|
||||||
|
* parent?: import('eslint').Rule.Node & {
|
||||||
|
* decorators?: any[]
|
||||||
|
* }
|
||||||
|
* }} node
|
||||||
|
* @returns {import('@typescript-eslint/types').TSESTree.Decorator|undefined}
|
||||||
|
*/
|
||||||
|
declare function getDecorator(
|
||||||
|
node: (ESLintOrTSNode | estree.Comment) & {
|
||||||
|
declaration?: any;
|
||||||
|
decorators?: any[];
|
||||||
|
parent?: eslint.Rule.Node & {
|
||||||
|
decorators?: any[];
|
||||||
|
};
|
||||||
|
},
|
||||||
|
): _typescript_eslint_types.TSESTree.Decorator | undefined;
|
||||||
|
/**
|
||||||
|
* Checks for the presence of a JSDoc comment for the given node and returns it.
|
||||||
|
*
|
||||||
|
* @param {ESLintOrTSNode} astNode The AST node to get
|
||||||
|
* the comment for.
|
||||||
|
* @param {import('eslint').SourceCode} sourceCode
|
||||||
|
* @param {{maxLines: int, minLines: int, [name: string]: any}} settings
|
||||||
|
* @param {{nonJSDoc?: boolean}} [opts]
|
||||||
|
* @returns {Token|null} The Block comment token containing the JSDoc comment
|
||||||
|
* for the given node or null if not found.
|
||||||
|
*/
|
||||||
|
declare function findJSDocComment(
|
||||||
|
astNode: ESLintOrTSNode,
|
||||||
|
sourceCode: eslint.SourceCode,
|
||||||
|
settings: {
|
||||||
|
maxLines: int;
|
||||||
|
minLines: int;
|
||||||
|
[name: string]: any;
|
||||||
|
},
|
||||||
|
opts?: {
|
||||||
|
nonJSDoc?: boolean;
|
||||||
|
},
|
||||||
|
): Token | null;
|
||||||
|
/**
|
||||||
|
* Checks for the presence of a comment following the given node and
|
||||||
|
* returns it.
|
||||||
|
*
|
||||||
|
* This method is experimental.
|
||||||
|
*
|
||||||
|
* @param {import('eslint').SourceCode} sourceCode
|
||||||
|
* @param {ESLintOrTSNode} astNode The AST node to get
|
||||||
|
* the comment for.
|
||||||
|
* @returns {Token|null} The comment token containing the comment
|
||||||
|
* for the given node or null if not found.
|
||||||
|
*/
|
||||||
|
declare function getFollowingComment(sourceCode: eslint.SourceCode, astNode: ESLintOrTSNode): Token | null;
|
||||||
|
|
||||||
|
declare function hasSeeWithLink(spec: comment_parser.Spec): boolean;
|
||||||
|
declare const defaultNoTypes: string[];
|
||||||
|
declare const defaultNoNames: string[];
|
||||||
|
/**
|
||||||
|
* Can't import `comment-parser/es6/parser/tokenizers/index.js`,
|
||||||
|
* so we redefine here.
|
||||||
|
*/
|
||||||
|
type CommentParserTokenizer = (spec: comment_parser.Spec) => comment_parser.Spec;
|
||||||
|
/**
|
||||||
|
* Can't import `comment-parser/es6/parser/tokenizers/index.js`,
|
||||||
|
* so we redefine here.
|
||||||
|
* @typedef {(spec: import('comment-parser').Spec) =>
|
||||||
|
* import('comment-parser').Spec} CommentParserTokenizer
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {object} [cfg]
|
||||||
|
* @param {string[]} [cfg.noTypes]
|
||||||
|
* @param {string[]} [cfg.noNames]
|
||||||
|
* @returns {CommentParserTokenizer[]}
|
||||||
|
*/
|
||||||
|
declare function getTokenizers({
|
||||||
|
noTypes,
|
||||||
|
noNames,
|
||||||
|
}?: {
|
||||||
|
noTypes?: string[];
|
||||||
|
noNames?: string[];
|
||||||
|
}): CommentParserTokenizer[];
|
||||||
|
/**
|
||||||
|
* Accepts a comment token or complete comment string and converts it into
|
||||||
|
* `comment-parser` AST.
|
||||||
|
* @param {string | {value: string}} commentOrNode
|
||||||
|
* @param {string} [indent] Whitespace
|
||||||
|
* @returns {import('.').JsdocBlockWithInline}
|
||||||
|
*/
|
||||||
|
declare function parseComment(
|
||||||
|
commentOrNode:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
value: string;
|
||||||
|
},
|
||||||
|
indent?: string,
|
||||||
|
): JsdocBlockWithInline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the `{@prefix}` from remaining `Spec.lines[].token.description`
|
||||||
|
* into the `inlineTags` tokens, and populates `spec.inlineTags`
|
||||||
|
* @param {import('comment-parser').Block} block
|
||||||
|
* @returns {import('.').JsdocBlockWithInline}
|
||||||
|
*/
|
||||||
|
declare function parseInlineTags(block: comment_parser.Block): JsdocBlockWithInline;
|
||||||
|
|
||||||
|
type InlineTag = JsdocInlineTagNoType & {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
|
type JsdocTagWithInline = comment_parser.Spec & {
|
||||||
|
line?: Integer;
|
||||||
|
inlineTags: (JsdocInlineTagNoType & {
|
||||||
|
line?: Integer;
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Expands on comment-parser's `Block` interface.
|
||||||
|
*/
|
||||||
|
type JsdocBlockWithInline = {
|
||||||
|
description: string;
|
||||||
|
source: comment_parser.Line[];
|
||||||
|
problems: comment_parser.Problem[];
|
||||||
|
tags: JsdocTagWithInline[];
|
||||||
|
inlineTags: (JsdocInlineTagNoType & {
|
||||||
|
line?: Integer;
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
type ESTreeToStringOptions = {
|
||||||
|
preferRawType?: boolean;
|
||||||
|
};
|
||||||
|
type CommentHandler = (commentSelector: string, jsdoc: JsdocBlockWithInline) => boolean;
|
||||||
|
|
||||||
|
export {
|
||||||
|
type CommentHandler,
|
||||||
|
type CommentParserTokenizer,
|
||||||
|
type ESLintOrTSNode,
|
||||||
|
type ESTreeToStringOptions,
|
||||||
|
type InlineTag,
|
||||||
|
type Integer,
|
||||||
|
JsdocBlock,
|
||||||
|
type JsdocBlockWithInline,
|
||||||
|
JsdocDescriptionLine,
|
||||||
|
JsdocInlineTag,
|
||||||
|
type JsdocInlineTagNoType,
|
||||||
|
JsdocTag,
|
||||||
|
type JsdocTagWithInline,
|
||||||
|
JsdocTypeLine,
|
||||||
|
type Token,
|
||||||
|
commentHandler,
|
||||||
|
commentParserToESTree,
|
||||||
|
defaultNoNames,
|
||||||
|
defaultNoTypes,
|
||||||
|
estreeToString,
|
||||||
|
findJSDocComment,
|
||||||
|
getDecorator,
|
||||||
|
getFollowingComment,
|
||||||
|
getJSDocComment,
|
||||||
|
getNonJsdocComment,
|
||||||
|
getReducedASTNode,
|
||||||
|
getTokenizers,
|
||||||
|
hasSeeWithLink,
|
||||||
|
type int,
|
||||||
|
jsdocVisitorKeys,
|
||||||
|
parseComment,
|
||||||
|
parseInlineTags,
|
||||||
|
};
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"name": "@es-joy/jsdoccomment",
|
||||||
|
"version": "0.46.0",
|
||||||
|
"author": "Brett Zamir <brettz9@yahoo.com>",
|
||||||
|
"contributors": [],
|
||||||
|
"description": "Maintained replacement for ESLint's deprecated SourceCode#getJSDocComment along with other jsdoc utilities",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"ast",
|
||||||
|
"comment",
|
||||||
|
"estree",
|
||||||
|
"jsdoc",
|
||||||
|
"parser",
|
||||||
|
"eslint",
|
||||||
|
"sourcecode"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./src/index.js",
|
||||||
|
"require": "./dist/index.cjs.cjs"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"cover 100%"
|
||||||
|
],
|
||||||
|
"typedocOptions": {
|
||||||
|
"dmtLinksService": {
|
||||||
|
"GitHub": "https://github.com/es-joy/jsdoccomment",
|
||||||
|
"NPM": "https://www.npmjs.com/package/@es-joy/jsdoccomment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/es-joy/jsdoccomment.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/es-joy/jsdoccomment/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/es-joy/jsdoccomment",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"comment-parser": "1.4.1",
|
||||||
|
"esquery": "^1.6.0",
|
||||||
|
"jsdoc-type-pratt-parser": "~4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.24.7",
|
||||||
|
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
||||||
|
"@babel/preset-env": "^7.24.7",
|
||||||
|
"@brettz9/eslint-plugin": "^1.0.4",
|
||||||
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
|
"@types/eslint": "^8.56.10",
|
||||||
|
"@types/esquery": "^1.5.4",
|
||||||
|
"@types/estraverse": "^5.1.7",
|
||||||
|
"@types/estree": "^1.0.5",
|
||||||
|
"@typescript-eslint/types": "^7.16.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "^7.16.0",
|
||||||
|
"@typhonjs-build-test/esm-d-ts": "0.3.0-next.1",
|
||||||
|
"@typhonjs-typedoc/typedoc-pkg": "^0.0.5",
|
||||||
|
"@vitest/coverage-v8": "^2.0.1",
|
||||||
|
"@vitest/ui": "^2.0.1",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-config-ash-nazg": "35.3.0",
|
||||||
|
"eslint-config-standard": "^17.1.0",
|
||||||
|
"eslint-plugin-array-func": "^4.0.0",
|
||||||
|
"eslint-plugin-compat": "^4.2.0",
|
||||||
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||||
|
"eslint-plugin-html": "^7.1.0",
|
||||||
|
"eslint-plugin-import": "^2.29.1",
|
||||||
|
"eslint-plugin-jsdoc": "^48.0.4",
|
||||||
|
"eslint-plugin-markdown": "^3.0.1",
|
||||||
|
"eslint-plugin-n": "^16.6.2",
|
||||||
|
"eslint-plugin-no-unsanitized": "^4.0.2",
|
||||||
|
"eslint-plugin-no-use-extend-native": "^0.5.0",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
|
"eslint-plugin-sonarjs": "^0.23.0",
|
||||||
|
"eslint-plugin-unicorn": "^50.0.1",
|
||||||
|
"espree": "^10.1.0",
|
||||||
|
"estraverse": "^5.3.0",
|
||||||
|
"rollup": "^4.18.1",
|
||||||
|
"typescript": "^5.5.3",
|
||||||
|
"typescript-eslint": "^7.16.0",
|
||||||
|
"vitest": "^2.0.1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"/dist",
|
||||||
|
"/src",
|
||||||
|
"CHANGES.md",
|
||||||
|
"LICENSE-MIT.txt"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c && npm run types",
|
||||||
|
"docs": "typedoc-pkg --api-link es",
|
||||||
|
"eslint": "eslint --ext=js,cjs,md,html .",
|
||||||
|
"lint": "npm run eslint --",
|
||||||
|
"open": "open ./coverage/index.html",
|
||||||
|
"test": "npm run lint && npm run build && npm run test-ui",
|
||||||
|
"test-ui": "vitest --ui --coverage",
|
||||||
|
"test-cov": "vitest --coverage",
|
||||||
|
"tsc": "tsc",
|
||||||
|
"types": "esm-d-ts gen ./src/index.js --output ./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||