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"
|
||||
}
|
||||
}
|
||||