Initial import

This commit is contained in:
2025-11-05 20:35:04 +01:00
commit 5b1fd847c2
4586 changed files with 685044 additions and 0 deletions
View File
+20
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 KiB

Binary file not shown.
Binary file not shown.
+1
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

+5
View File
@@ -0,0 +1,5 @@
## v13.0.12
- Fix favor/disfavor
- Fix granted dice
- Cosmetic fixes
File diff suppressed because it is too large Load Diff
+219
View File
@@ -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
];
+33
View File
@@ -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;
+879
View File
@@ -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"
}
}
}
+12
View File
@@ -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"
+220
View File
@@ -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
}
}
+22
View File
@@ -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
}
}
+5
View File
@@ -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" }
})
+76
View File
@@ -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"
}
})
File diff suppressed because it is too large Load Diff
+51
View File
@@ -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",
},
})
+60
View File
@@ -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"
}
})
+22
View File
@@ -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",
}
})
+22
View File
@@ -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",
},
})
+327
View File
@@ -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
}
+35
View File
@@ -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"]
}
+4
View File
@@ -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"
+196
View File
@@ -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)
}
}
+22
View File
@@ -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)
}
}
+20
View File
@@ -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);
}
}
File diff suppressed because it is too large Load Diff
+80
View File
@@ -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
}
+82
View File
@@ -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)
}
}
}
+11
View File
@@ -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"
+28
View File
@@ -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"]
}
+352
View File
@@ -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",
})
}
}
+25
View File
@@ -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"]
}
+16
View File
@@ -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"]
}
+44
View File
@@ -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"]
}
+281
View File
@@ -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 }) })
}
}
}
+44
View File
@@ -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"]
}
+114
View File
@@ -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)}`;
}
}
+48
View File
@@ -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"]
}
+17
View File
@@ -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"]
}
+68
View File
@@ -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)
}
}
+249
View File
@@ -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
}
}
}
}
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../acorn/bin/acorn
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../errno/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../eslint/bin/eslint.js
+1
View File
@@ -0,0 +1 @@
../eslint-config-prettier/bin/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../@foundryvtt/foundryvtt-cli/fvtt.mjs
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../gulp/bin/gulp.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../image-size/bin/image-size.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../js-yaml/bin/js-yaml.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../less/bin/lessc
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../mime/cli.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../mkdirp/dist/cjs/src/bin.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../needle/bin/needle
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../node-gyp-build/bin.js
+1
View File
@@ -0,0 +1 @@
../node-gyp-build/optional.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../node-gyp-build/build-test.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../which/bin/node-which
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../prettier/bin/prettier.cjs
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../resolve/bin/resolve
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver.js
+3465
View File
File diff suppressed because it is too large Load Diff
+589
View File
@@ -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
+20
View File
@@ -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.
+241
View File
@@ -0,0 +1,241 @@
# @es-joy/jsdoccomment
[![NPM](https://img.shields.io/npm/v/@es-joy/jsdoccomment.svg?label=npm)](https://www.npmjs.com/package/@es-joy/jsdoccomment)
[![License](https://img.shields.io/badge/license-MIT-yellowgreen.svg?style=flat)](https://github.com/es-joy/jsdoccomment/blob/main/LICENSE-MIT.txt)
[![Build Status](https://github.com/es-joy/jsdoccomment/workflows/CI/CD/badge.svg)](#)
[![API Docs](https://img.shields.io/badge/API%20Documentation-476ff0)](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
File diff suppressed because it is too large Load Diff
+361
View File
@@ -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,
};
+106
View File
@@ -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"
}
}

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