Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d1a4268bc | |||
| b9e6fc5075 | |||
| 56233e7c10 | |||
| 694a09d603 | |||
| e662242000 | |||
| 3187d625f0 | |||
| cba485285d | |||
| b7b8b5c54a | |||
| 59f9022e43 | |||
| 0153896eeb | |||
| 7775932702 | |||
| c670bd5dc6 | |||
| 03787ee314 | |||
| 469fad5ba5 | |||
| b41fd8833b | |||
| 0560cc5778 | |||
| f396636713 | |||
| c7193b3306 | |||
| 15436de599 | |||
| 298d892844 | |||
| a47b765c6f | |||
| 669c3799a0 | |||
| be74c79b35 | |||
| f900cb7d53 | |||
| a2fa9d3b05 | |||
| 7240fcf041 | |||
| e18419575c | |||
| ec4f3e6c63 | |||
| 9e09eaecbb | |||
| d3274905c1 | |||
| 78ce8661a4 | |||
| d8b9f554ed | |||
| 1352d62c4f | |||
| ba9eac716d | |||
| cd8b5ac7eb | |||
| f93418009d | |||
| a0bc314e0d | |||
| 89c357d6da | |||
| 88eac4f241 | |||
| 281ab0712e | |||
| 5427d738ff | |||
| 1c4595c72f | |||
| 1b4e07ef65 | |||
| f2a088390f | |||
| 26be798ae2 | |||
| 93d13a0b47 | |||
| 8e9827e5ae | |||
| abfed3204b | |||
| 22f6a49c65 | |||
| 4372c5460a | |||
| 969dab0a3e | |||
| 3a91311887 | |||
| 4e37f910a1 | |||
| 141b06adea | |||
| e240bb5bf1 | |||
| 2765a53815 | |||
| 06f26c264c | |||
| d14de3d385 | |||
| 39c2ce8d70 | |||
| 2ef597dc8c | |||
| 65d8ec2d39 | |||
| 8e206d1180 | |||
| 2721eb4a1e | |||
| f9824ad02e | |||
| b1a128b692 | |||
| 318cc36348 | |||
| 225b081f3c | |||
| 8f557297eb | |||
| 551161c6df | |||
| 946158a145 | |||
| 43cfa125ea | |||
| 94ee4243fa | |||
| 18cd705626 | |||
| 5111bd830c | |||
| 557cb6e546 | |||
| cc14b28d40 | |||
| d520f8e137 | |||
| 613552af4e | |||
| c68c55bfc7 | |||
| fe0e889c02 | |||
| 20871bcdef | |||
| 5cb15bcedd | |||
| 5965b89b66 | |||
| 929cdda415 | |||
| 57e6ab1bd3 | |||
| 1cb7abb7f5 | |||
| d5300a845b | |||
| 9ad0a23c33 | |||
| 4959a380ab | |||
| ba99dc08e2 | |||
| f4bfa13cab | |||
| e016437b73 | |||
| a6e2bc7bdc | |||
| 6fe743130d | |||
| 4bdd6176e8 | |||
| ac70a99b99 | |||
| b646524a97 | |||
| 805f50c631 | |||
| a8354b4983 | |||
| d6581e16d4 | |||
| 140381e1d1 | |||
| 0c0fb1a3a8 | |||
| 4ffed673db | |||
| 5c9556471e | |||
| 8aac513792 | |||
| e438fd36e8 | |||
| 08d2be4ea0 | |||
| 06306d8959 | |||
| 267e892f27 | |||
| 1207b9c1bd | |||
| 61e78936a7 | |||
| 7c28c43bd9 | |||
| e0cd1c640c | |||
| 864acdd722 | |||
| f80e551835 | |||
| 1cd4a08990 | |||
| 4908c6d4a7 | |||
| 27989ffdfb | |||
| ef1eb940d0 | |||
| d7f6582d41 | |||
| 3d6a561929 | |||
| b4fd301a4f | |||
| 3062e1bfcb | |||
| d85c11088a | |||
| ae3bc394e7 | |||
| 36520fcee2 | |||
| 4b087916b3 | |||
| 0d74ae787d | |||
| c76ce1ff7c | |||
| 53563af7d0 | |||
| b6a42818c8 | |||
| 909457d1dd | |||
| 6a7dd696ca | |||
| e92a2c3424 | |||
| 2dda9d76a4 | |||
| caee516d48 | |||
| 0d41527a5e | |||
| 2870b6b587 | |||
| db471b0cb9 | |||
| b6c16ed0f5 | |||
| a1246bf758 | |||
| 3357145ea9 | |||
| 4c00d7116e | |||
| 5151634e8b | |||
| 486a7d4167 | |||
| 1877b048d2 | |||
| 8c373fcc8b | |||
| 57caeccad2 | |||
| 2caa2a4633 | |||
| dea2fa95c4 | |||
| 7ee2f966c0 | |||
| f50a81a329 | |||
| cf30214502 | |||
| eaf5b8a066 | |||
| 6ec4d9019e | |||
| 8f015bc05c | |||
| ae7295028d | |||
| f962ee785e | |||
| b5b0ad01f6 | |||
| c8f27770b6 | |||
| 53d30e80e9 | |||
| 99657ea07f | |||
| a3fdf1c7f2 | |||
| 4c93c0be94 | |||
| 46feb5705e | |||
| d4f155adc2 | |||
| 4d44b6c31c | |||
| eccb4778cb | |||
| 13e6c9bcf7 | |||
| 312f2a88fc | |||
| 69bea4e77d | |||
| e6f37b173f | |||
| dd4735d50a | |||
| ca7acd7e9d | |||
| 5dfce6cbcc | |||
| 6100f425ed | |||
| ba7f3302c1 | |||
| 13f93898d6 | |||
| 8e50b478ce | |||
| d99d0c0728 | |||
| c679304ae2 | |||
| baa9d51c18 | |||
| 592d0c5406 | |||
| 2aa7af28a5 | |||
| 9333be95fd | |||
| b4089e4b71 | |||
| 391b76ccbd | |||
| 6fb821cc2a | |||
| df607d4d66 | |||
| b5f0794866 | |||
| 14c988c528 | |||
| a3733fa4cc | |||
| 6b5b1195ab | |||
| 4de06dac03 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
15
.eslintrc.js
15
.eslintrc.js
@ -1,15 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2021: true
|
|
||||||
},
|
|
||||||
extends: 'standard',
|
|
||||||
overrides: [
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest',
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
146
.gitignore
vendored
146
.gitignore
vendored
@ -1,132 +1,32 @@
|
|||||||
# ---> Node
|
# SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
# Logs
|
#
|
||||||
logs
|
# SPDX-License-Identifier: MIT
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# IDE
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
.idea/
|
||||||
|
.vs/
|
||||||
|
|
||||||
# Runtime data
|
# Node Modules
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
*.lcov
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
npm-debug.log
|
||||||
|
|
||||||
# Snowpack dependency directory (https://snowpack.dev/)
|
# yarn2
|
||||||
web_modules/
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
# TypeScript cache
|
# Local configuration
|
||||||
*.tsbuildinfo
|
foundryconfig.json
|
||||||
|
|
||||||
# Optional npm cache directory
|
# Distribution files
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional stylelint cache
|
|
||||||
.stylelintcache
|
|
||||||
|
|
||||||
# Microbundle cache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variable files
|
|
||||||
.env
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
|
||||||
.cache
|
|
||||||
.parcel-cache
|
|
||||||
|
|
||||||
# Next.js build output
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
|
|
||||||
# Nuxt.js build / generate output
|
|
||||||
.nuxt
|
|
||||||
dist
|
dist
|
||||||
|
|
||||||
# Gatsby files
|
# ESLint
|
||||||
.cache/
|
.eslintcache
|
||||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
|
||||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
|
||||||
# public
|
|
||||||
|
|
||||||
# vuepress build output
|
# Junit results
|
||||||
.vuepress/dist
|
junit.xml
|
||||||
|
|
||||||
# vuepress v2.x temp and cache directory
|
|
||||||
.temp
|
|
||||||
.cache
|
|
||||||
|
|
||||||
# Docusaurus cache and generated files
|
|
||||||
.docusaurus
|
|
||||||
|
|
||||||
# Serverless directories
|
|
||||||
.serverless/
|
|
||||||
|
|
||||||
# FuseBox cache
|
|
||||||
.fusebox/
|
|
||||||
|
|
||||||
# DynamoDB Local files
|
|
||||||
.dynamodb/
|
|
||||||
|
|
||||||
# TernJS port file
|
|
||||||
.tern-port
|
|
||||||
|
|
||||||
# Stores VSCode versions used for testing VSCode extensions
|
|
||||||
.vscode-test
|
|
||||||
|
|
||||||
# yarn v2
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
|
||||||
swade-mb-helpers.lock
|
|
||||||
|
|||||||
5
.gulp.json
Normal file
5
.gulp.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"flags": {
|
||||||
|
"gulpfile": "gulpfile.mjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
.prettierignore
Normal file
8
.prettierignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
dist
|
||||||
|
package-lock.json
|
||||||
|
.pnp.cjs
|
||||||
|
.pnp.js
|
||||||
11
.prettierrc.cjs
Normal file
11
.prettierrc.cjs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
semi: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 120,
|
||||||
|
tabWidth: 2,
|
||||||
|
};
|
||||||
160
CHANGELOG.md
160
CHANGELOG.md
@ -5,6 +5,166 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [4.1.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- added suppression of system gang up calculation
|
||||||
|
- added correction for gang up for formation fighter
|
||||||
|
- added swat correction for scale penalty
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- updated roll modifiers for SWADE version 5.1.0
|
||||||
|
- updated pack tactics gang up to use same calculation as system
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- removed roll modifiers handled by the system:
|
||||||
|
- range check
|
||||||
|
- vulnerable target
|
||||||
|
- dodge check
|
||||||
|
|
||||||
|
## [4.0.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added example morphables and summonables actor compendia.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated for Foundry v13
|
||||||
|
- Updated for SWADE 5.0
|
||||||
|
- Updated powerEffect application and other dialogs to ApplicationV2
|
||||||
|
- BREAKING CHANGE: removed Portal dependency for summons
|
||||||
|
- BREAKING CHANGE: added Sequencer dependency for summons
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Darksight effect name.
|
||||||
|
- Summon Ally: Mirror Self improvements - remove changesets and grants from
|
||||||
|
copied items.
|
||||||
|
|
||||||
|
## [3.1.5] 2025-01-27
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added flying pace support to Fly power
|
||||||
|
- Added burrowing pace support to Burrow power
|
||||||
|
- Added additional icon choices to the list for maintain effects
|
||||||
|
|
||||||
|
## [3.1.4] 2025-01-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed module loding with swade core module and swpf due to fear macro changes
|
||||||
|
- Fixed power effect creation for SWADE system 4.2
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Clean up of requested roll GM chat card
|
||||||
|
- Make Deflection roll helper modifiers more descriptive (melee, ranged, all)
|
||||||
|
|
||||||
|
## [3.1.3] 2025-01-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Updated hurry/hinder and sloth/speed power effects to work with
|
||||||
|
`system.pace` for SWADE 4.2 compatibility.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- updated settings for Blindsense to a more pleasing look.
|
||||||
|
|
||||||
|
## [3.1.2] 2024-08-11
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #44: protection effect and other default system effects still had duration
|
||||||
|
when added by the power effect, instead of maintaining their own effect.
|
||||||
|
- Since VAE did away with the collapsable extra description, moved the spell
|
||||||
|
description to the main 'description' field of active effects.
|
||||||
|
|
||||||
|
## [3.1.1] 2024-07-26
|
||||||
|
|
||||||
|
- Added: 'Shape Change Ability' power effect
|
||||||
|
|
||||||
|
## [3.1.0] 2024-07-14
|
||||||
|
|
||||||
|
- v12 compatibility
|
||||||
|
- bugfixes for v12
|
||||||
|
|
||||||
|
## [3.0.2] 2024-06-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed bug in which effects would disappear on the target's next turn
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Eidolon summon effect and helper action
|
||||||
|
- Companion "summon" effect and helper action
|
||||||
|
|
||||||
|
## [3.0.1] 2024-06-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added Inquisitor's Judgement power effect
|
||||||
|
|
||||||
|
## [3.0.0] 2024-06-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Optional Visual Active Effect integration for power descriptions
|
||||||
|
- Add VAE helper buttons for breaking free from Bound/Entangled
|
||||||
|
- Added Portal dependency
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Refactor and redo of powers handling
|
||||||
|
- maintained powers
|
||||||
|
- powerpoint calculation
|
||||||
|
- more powers
|
||||||
|
- Refactor of distribution
|
||||||
|
- Code is minified into one file for faster loading
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed warpgate dependency
|
||||||
|
|
||||||
|
## [2.4.3] 2024-04-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added token varient art suggested effect mappings
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Added elevation component to distance calculations for gang up
|
||||||
|
- Added check for incapacitated or defeated tokens to gang up check
|
||||||
|
- Minor cosmetic change to 'Draw from Action Deck' macro
|
||||||
|
|
||||||
|
## [2.4.2] 2024-02-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added Macro Manager macro for all Power Effects
|
||||||
|
- Added Macro Manager macro for all helper macros
|
||||||
|
- Added Draw from Action Deck macro
|
||||||
|
- Added Shuffle Action Deck macro
|
||||||
|
|
||||||
|
## [2.4.1] 2024-02-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for SWPF Smite's Spiritual Weapon (from the APG)
|
||||||
|
|
||||||
|
## [2.4.0] 2024-02-11
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added Monk's Active Tile Trigger versions of the request roll macros.
|
||||||
|
|
||||||
## [2.3.5] 2024-01-30
|
## [2.3.5] 2024-01-30
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
73
README.md
73
README.md
@ -3,3 +3,76 @@
|
|||||||
This is a series of helper macros and other helpers to help run SWADE and
|
This is a series of helper macros and other helpers to help run SWADE and
|
||||||
Savage Pathfinder games with some minimal help remembering token state, without
|
Savage Pathfinder games with some minimal help remembering token state, without
|
||||||
going overboard on the automation.
|
going overboard on the automation.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
In order to build this module, recent versions of `node` and `npm` are
|
||||||
|
required. Most likely, using `yarn` also works, but only `npm` is officially
|
||||||
|
supported. We recommend using the latest lts version of `node`. If you use `nvm`
|
||||||
|
to manage your `node` versions, you can simply run
|
||||||
|
|
||||||
|
```
|
||||||
|
nvm install
|
||||||
|
```
|
||||||
|
|
||||||
|
in the project's root directory.
|
||||||
|
|
||||||
|
You also need to install the project's dependencies. To do so, run
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
You can build the project by running
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can run
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build:watch
|
||||||
|
```
|
||||||
|
|
||||||
|
to watch for changes and automatically build as necessary.
|
||||||
|
|
||||||
|
### Linking the built project to Foundry VTT
|
||||||
|
|
||||||
|
In order to provide a fluent development experience, it is recommended to link
|
||||||
|
the built module to your local Foundry VTT installation's data folder. In
|
||||||
|
order to do so, first add a file called `foundryconfig.json` to the project root
|
||||||
|
with the following content:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"dataPath": ["/absolute/path/to/your/FoundryVTT"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
(if you are using Windows, make sure to use `\` as a path separator instead of
|
||||||
|
`/`)
|
||||||
|
|
||||||
|
Then run
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run link-project
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows, creating symlinks requires administrator privileges, so
|
||||||
|
unfortunately you need to run the above command in an administrator terminal for
|
||||||
|
it to work.
|
||||||
|
|
||||||
|
You can also link to multiple data folders by specifying multiple paths in the
|
||||||
|
`dataPath` array.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
This project is being developed under the terms of the
|
||||||
|
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT] for Foundry Virtual Tabletop.
|
||||||
|
|
||||||
|
[LIMITED LICENSE AGREEMENT FOR MODULE DEVELOPMENT]: https://foundryvtt.com/article/license/
|
||||||
|
|||||||
@ -1,660 +0,0 @@
|
|||||||
{
|
|
||||||
"globalMappings": [
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 30000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/Item/GlintMany01_01_Regular_Yellow_200x200.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0.38,
|
|
||||||
"scaleX": 0.4,
|
|
||||||
"scaleY": 0.4,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "DropShadowFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"rotation": 45,
|
|
||||||
"distance": 7,
|
|
||||||
"color": 0,
|
|
||||||
"alpha": 0.84,
|
|
||||||
"shadowOnly": false,
|
|
||||||
"blur": 2,
|
|
||||||
"quality": 0
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "Shaken",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "mwFtNKpD"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Shaken",
|
|
||||||
"expression": "Shaken",
|
|
||||||
"id": "mwFtNKpD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": true,
|
|
||||||
"duration": 30000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/Particles/ParticlesInward02_04_Regular_GreenYellow_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 1.5,
|
|
||||||
"scaleY": 1.5,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "DropShadowFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"rotation": 45,
|
|
||||||
"distance": 7,
|
|
||||||
"color": 0,
|
|
||||||
"alpha": 0.84,
|
|
||||||
"shadowOnly": false,
|
|
||||||
"blur": 2,
|
|
||||||
"quality": 0
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "toTYr3DQ"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Distracted",
|
|
||||||
"expression": "Distracted",
|
|
||||||
"id": "toTYr3DQ"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5200,
|
|
||||||
"clockwise": false
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/Marker/MarkerShieldCracked_02_Regular_Purple_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 1.5,
|
|
||||||
"scaleY": 1.5,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "OutlineFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"thickness": 1,
|
|
||||||
"color": 0,
|
|
||||||
"quality": 0.1
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "J4GrRaxL"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Vulnerable",
|
|
||||||
"expression": "Vulnerable",
|
|
||||||
"id": "J4GrRaxL"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/UI/IconStun_01_Regular_Purple_200x200.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 0.77,
|
|
||||||
"scaleY": 0.77,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "OutlineFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"thickness": 1,
|
|
||||||
"color": 0,
|
|
||||||
"quality": 0.1
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "nOfPMsQp"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Stunned",
|
|
||||||
"expression": "Stunned",
|
|
||||||
"id": "nOfPMsQp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/1st_Level/Entangle/Entangle_01_Green_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 1,
|
|
||||||
"scaleY": 1,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "DropShadowFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"rotation": 45,
|
|
||||||
"distance": 2,
|
|
||||||
"color": 2367281,
|
|
||||||
"alpha": 0.84,
|
|
||||||
"shadowOnly": false,
|
|
||||||
"blur": 2,
|
|
||||||
"quality": 0
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "rrdhKai4"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Entangled",
|
|
||||||
"expression": "Entangled",
|
|
||||||
"id": "rrdhKai4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/Marker/MarkerChainStandard01_01_Regular_Red_Loop_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 1.75,
|
|
||||||
"scaleY": 1.75,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "DropShadowFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"rotation": 45,
|
|
||||||
"distance": 2,
|
|
||||||
"color": 2367281,
|
|
||||||
"alpha": 0.84,
|
|
||||||
"shadowOnly": false,
|
|
||||||
"blur": 2,
|
|
||||||
"quality": 0
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "5RYi2X9W"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Bound",
|
|
||||||
"expression": "Bound",
|
|
||||||
"id": "5RYi2X9W"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {
|
|
||||||
"tv_script": {
|
|
||||||
"onApply": "",
|
|
||||||
"onRemove": "",
|
|
||||||
"tmfxPreset": "dropshadow"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overlay": false,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"id": "KtequnXd"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Flying",
|
|
||||||
"expression": "Flying",
|
|
||||||
"id": "KtequnXd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {
|
|
||||||
"tv_script": {
|
|
||||||
"onApply": "",
|
|
||||||
"onRemove": "",
|
|
||||||
"tmfxPreset": "glow"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overlay": false,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"id": "k4boMPSb"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Conviction",
|
|
||||||
"expression": "Conviction",
|
|
||||||
"id": "k4boMPSb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {
|
|
||||||
"light": {
|
|
||||||
"dim": 0.25,
|
|
||||||
"bright": 0,
|
|
||||||
"color": "#1c71d8",
|
|
||||||
"alpha": 0.4,
|
|
||||||
"animation": {
|
|
||||||
"type": "sunburst",
|
|
||||||
"speed": 3,
|
|
||||||
"intensity": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": true,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Cantrip/Dancing_Lights/DancingLights_01_Yellow_200x200.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0.52,
|
|
||||||
"scaleX": 0.41,
|
|
||||||
"scaleY": 0.41,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "NONE",
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "eO68BGDl"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Glow",
|
|
||||||
"expression": "Glow",
|
|
||||||
"id": "eO68BGDl"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {
|
|
||||||
"texture": {
|
|
||||||
"tint": "#c0bfbc"
|
|
||||||
},
|
|
||||||
"tv_script": {
|
|
||||||
"onApply": "",
|
|
||||||
"onRemove": "",
|
|
||||||
"tmfxPreset": "smoke"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overlay": false,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"id": "BP0Xx8wD"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Shroud",
|
|
||||||
"expression": "Shroud",
|
|
||||||
"id": "BP0Xx8wD"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/Generic/Marker/MarkerShield_03_Regular_Green_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 2,
|
|
||||||
"scaleY": 2,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "OutlineFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"thickness": 1,
|
|
||||||
"color": 0,
|
|
||||||
"quality": 0.1
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "gRwsZcZK"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Deflection (melee) || Deflection (range) || Deflection (raise)",
|
|
||||||
"expression": "Deflection (melee) || Deflection (range) || Deflection (raise)",
|
|
||||||
"id": "gRwsZcZK"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"imgName": "",
|
|
||||||
"imgSrc": "",
|
|
||||||
"priority": 50,
|
|
||||||
"config": {},
|
|
||||||
"overlay": true,
|
|
||||||
"alwaysOn": false,
|
|
||||||
"overlayConfig": {
|
|
||||||
"underlay": false,
|
|
||||||
"bottom": false,
|
|
||||||
"top": false,
|
|
||||||
"inheritTint": false,
|
|
||||||
"linkRotation": true,
|
|
||||||
"animation": {
|
|
||||||
"relative": false,
|
|
||||||
"rotate": false,
|
|
||||||
"duration": 5000,
|
|
||||||
"clockwise": true
|
|
||||||
},
|
|
||||||
"linkMirror": true,
|
|
||||||
"linkScale": true,
|
|
||||||
"linkOpacity": false,
|
|
||||||
"loop": true,
|
|
||||||
"playOnce": false,
|
|
||||||
"img": "modules/JB2A_DnD5e/Library/1st_Level/Shield/Shield_01_Regular_Blue_Loop_400x400.webm",
|
|
||||||
"alpha": 1,
|
|
||||||
"tint": "",
|
|
||||||
"offsetX": 0,
|
|
||||||
"offsetY": 0,
|
|
||||||
"scaleX": 1.6,
|
|
||||||
"scaleY": 1.6,
|
|
||||||
"angle": 0,
|
|
||||||
"filter": "OutlineFilter",
|
|
||||||
"filterOptions": {
|
|
||||||
"thickness": 2.9,
|
|
||||||
"color": 0,
|
|
||||||
"quality": 0.1
|
|
||||||
},
|
|
||||||
"alwaysVisible": false,
|
|
||||||
"limitedUsers": [],
|
|
||||||
"limitOnHover": false,
|
|
||||||
"limitOnControl": false,
|
|
||||||
"text": {
|
|
||||||
"text": "",
|
|
||||||
"fontFamily": "Signika",
|
|
||||||
"fontSize": 36,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"fill": "#FFFFFF",
|
|
||||||
"dropShadow": "true",
|
|
||||||
"strokeThickness": 1,
|
|
||||||
"stroke": "#111111",
|
|
||||||
"curve": {
|
|
||||||
"radius": 0,
|
|
||||||
"invert": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"id": "1po9hq1m"
|
|
||||||
},
|
|
||||||
"targetActors": null,
|
|
||||||
"label": "Protection",
|
|
||||||
"expression": "Protection",
|
|
||||||
"id": "1po9hq1m"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import globals from "globals"
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import typhonJs from "@typhonjs-fvtt/eslint-config-foundry.js"
|
||||||
|
export default [
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...typhonJs.globals,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ignores: ['dist/*', './macros/*'],
|
||||||
|
rules: {
|
||||||
|
'no-unused-vars': 'warn',
|
||||||
|
'no-undef': 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
];
|
||||||
12
fvtt-pack.sh
Executable file
12
fvtt-pack.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
curdir=$(realpath $(dirname $0))
|
||||||
|
package=$(basename ${curdir})
|
||||||
|
|
||||||
|
fvtt package workon ${package}
|
||||||
|
for p in ${curdir}/dist/swade-mb-helpers/packs/*; do
|
||||||
|
package=$(basename $p)
|
||||||
|
rm $p/*
|
||||||
|
fvtt package pack -n ${package} --inputDirectory ${curdir}/src/packsrc/${package} --clean
|
||||||
|
done
|
||||||
|
rsync -rpa ${curdir}/dist/swade-mb-helpers/packs/ ${curdir}/src/packs --delete
|
||||||
11
fvtt-unpack.sh
Executable file
11
fvtt-unpack.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
curdir=$(realpath $(dirname $0))
|
||||||
|
package=$(basename ${curdir})
|
||||||
|
|
||||||
|
fvtt package workon ${package}
|
||||||
|
for p in ${curdir}/dist/swade-mb-helpers/packs/*; do
|
||||||
|
package=$(basename $p)
|
||||||
|
mkdir -p ${curdir}/src/packsrc/${package}
|
||||||
|
fvtt package unpack -n ${package} --outputDirectory ${curdir}/src/packsrc/${package}
|
||||||
|
done
|
||||||
183
gulpfile.mjs
Normal file
183
gulpfile.mjs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import fs from 'fs-extra';
|
||||||
|
import gulp from 'gulp';
|
||||||
|
import sass from 'gulp-dart-sass';
|
||||||
|
import sourcemaps from 'gulp-sourcemaps';
|
||||||
|
import path from 'node:path';
|
||||||
|
import buffer from 'vinyl-buffer';
|
||||||
|
import source from 'vinyl-source-stream';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
|
||||||
|
import rollupStream from '@rollup/stream';
|
||||||
|
|
||||||
|
import rollupConfig from './rollup.config.mjs';
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* CONFIGURATION */
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
const packageId = 'swade-mb-helpers';
|
||||||
|
const sourceDirectory = './src';
|
||||||
|
const distDirectory = './dist/swade-mb-helpers';
|
||||||
|
const stylesDirectory = `${sourceDirectory}/styles`;
|
||||||
|
const stylesExtension = 'scss';
|
||||||
|
const sourceFileExtension = 'js';
|
||||||
|
const staticFiles = ['assets', 'config', 'fonts', 'lang', 'templates'];
|
||||||
|
const runtimeStaticFiles = ['packs', 'module.json'];
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* BUILD */
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
let cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the distributable JavaScript code
|
||||||
|
*/
|
||||||
|
function buildCode() {
|
||||||
|
return rollupStream({ ...rollupConfig(), cache })
|
||||||
|
.on('bundle', (bundle) => {
|
||||||
|
cache = bundle;
|
||||||
|
})
|
||||||
|
.pipe(source(`${packageId}.js`))
|
||||||
|
.pipe(buffer())
|
||||||
|
.pipe(sourcemaps.init({ loadMaps: true }))
|
||||||
|
.pipe(sourcemaps.write('.'))
|
||||||
|
.pipe(gulp.dest(`${distDirectory}/module`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build style sheets
|
||||||
|
*/
|
||||||
|
function buildStyles() {
|
||||||
|
return gulp
|
||||||
|
.src(`${stylesDirectory}/${packageId}.${stylesExtension}`)
|
||||||
|
.pipe(sass().on('error', sass.logError))
|
||||||
|
.pipe(gulp.dest(`${distDirectory}/styles`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy static files
|
||||||
|
*/
|
||||||
|
async function copyFiles() {
|
||||||
|
for (const file of staticFiles) {
|
||||||
|
if (fs.existsSync(`${sourceDirectory}/${file}`)) {
|
||||||
|
await fs.copy(`${sourceDirectory}/${file}`, `${distDirectory}/${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy runtime files
|
||||||
|
*/
|
||||||
|
async function copyRuntimeFiles() {
|
||||||
|
for (const file of runtimeStaticFiles) {
|
||||||
|
if (fs.existsSync(`${sourceDirectory}/${file}`)) {
|
||||||
|
await fs.copy(`${sourceDirectory}/${file}`, `${distDirectory}/${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch for changes for each build step
|
||||||
|
*/
|
||||||
|
export function watch() {
|
||||||
|
gulp.watch(`${sourceDirectory}/**/*.${sourceFileExtension}`, { ignoreInitial: false }, buildCode);
|
||||||
|
gulp.watch(`${stylesDirectory}/**/*.${stylesExtension}`, { ignoreInitial: false }, buildStyles);
|
||||||
|
gulp.watch(
|
||||||
|
staticFiles.map((file) => `${sourceDirectory}/${file}`),
|
||||||
|
{ ignoreInitial: false },
|
||||||
|
copyFiles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const build = gulp.series(clean, gulp.parallel(buildCode, buildStyles, copyRuntimeFiles, copyFiles));
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* CLEAN */
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove built files from `dist` folder while ignoring source files
|
||||||
|
*/
|
||||||
|
export async function clean() {
|
||||||
|
const files = [...runtimeStaticFiles, 'module'];
|
||||||
|
|
||||||
|
if (fs.existsSync(`${stylesDirectory}/${packageId}.${stylesExtension}`)) {
|
||||||
|
files.push('styles');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(' ', 'Files to clean:');
|
||||||
|
console.log(' ', files.join('\n '));
|
||||||
|
|
||||||
|
for (const filePath of files) {
|
||||||
|
await fs.remove(`${distDirectory}/${filePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************/
|
||||||
|
/* LINK */
|
||||||
|
/********************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data paths of Foundry VTT based on what is configured in `foundryconfig.json`
|
||||||
|
*/
|
||||||
|
function getDataPaths() {
|
||||||
|
const config = fs.readJSONSync('foundryconfig.json');
|
||||||
|
const dataPath = config?.dataPath;
|
||||||
|
|
||||||
|
if (dataPath) {
|
||||||
|
const dataPaths = Array.isArray(dataPath) ? dataPath : [dataPath];
|
||||||
|
|
||||||
|
return dataPaths.map((dataPath) => {
|
||||||
|
if (typeof dataPath !== 'string') {
|
||||||
|
throw new Error(
|
||||||
|
`Property dataPath in foundryconfig.json is expected to be a string or an array of strings, but found ${dataPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(path.resolve(dataPath))) {
|
||||||
|
throw new Error(`The dataPath ${dataPath} does not exist on the file system`);
|
||||||
|
}
|
||||||
|
return path.resolve(dataPath);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('No dataPath defined in foundryconfig.json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link build to User Data folder
|
||||||
|
*/
|
||||||
|
export async function link() {
|
||||||
|
let destinationDirectory;
|
||||||
|
if (fs.existsSync(path.resolve(sourceDirectory, 'module.json'))) {
|
||||||
|
destinationDirectory = 'modules';
|
||||||
|
} else {
|
||||||
|
throw new Error('Could not find module.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkDirectories = getDataPaths().map((dataPath) =>
|
||||||
|
path.resolve(dataPath, 'Data', destinationDirectory, packageId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const argv = yargs(hideBin(process.argv)).option('clean', {
|
||||||
|
alias: 'c',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
}).argv;
|
||||||
|
const clean = argv.c;
|
||||||
|
|
||||||
|
for (const linkDirectory of linkDirectories) {
|
||||||
|
if (clean) {
|
||||||
|
console.log(`Removing build in ${linkDirectory}.`);
|
||||||
|
|
||||||
|
await fs.remove(linkDirectory);
|
||||||
|
} else if (!fs.existsSync(linkDirectory)) {
|
||||||
|
console.log(`Linking dist to ${linkDirectory}.`);
|
||||||
|
await fs.ensureDir(path.resolve(linkDirectory, '..'));
|
||||||
|
await fs.symlink(path.resolve(distDirectory), linkDirectory);
|
||||||
|
} else {
|
||||||
|
console.log(`Skipped linking to ${linkDirectory}, as it already exists.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
macros/addEffectToPowers.js
Normal file
28
macros/addEffectToPowers.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const tokens = canvas.tokens.controlled;
|
||||||
|
const actors = tokens.map((t) => t.actor);
|
||||||
|
const EFFECT_OBJECT = {
|
||||||
|
name: 'Effect',
|
||||||
|
type: 'macro',
|
||||||
|
dice: null,
|
||||||
|
resourcesUsed: null,
|
||||||
|
modifier: '',
|
||||||
|
override: '',
|
||||||
|
uuid: 'Compendium.swade-mb-helpers.helper-macros.Macro.AjuA11hQ48UJNwlH',
|
||||||
|
macroActor: 'self',
|
||||||
|
isHeavyWeapon: false,
|
||||||
|
};
|
||||||
|
for (const actor of actors) {
|
||||||
|
const updates = [];
|
||||||
|
for (const power of actor.items.filter((i) => i.type === 'power')) {
|
||||||
|
if (Object.values(power.system.actions.additional).find((action) => action.name === 'Effect')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const _id = power.id;
|
||||||
|
const additional = foundry.utils.deepClone(power.system.actions.additional);
|
||||||
|
additional[foundry.utils.randomID(8)] = foundry.utils.deepClone(EFFECT_OBJECT);
|
||||||
|
updates.push({ _id, 'system.actions.additional': additional });
|
||||||
|
}
|
||||||
|
if (updates.length > 0) {
|
||||||
|
actor.updateEmbeddedDocuments('Item', updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,33 +1,33 @@
|
|||||||
let tokens = []
|
let tokens = [];
|
||||||
if (canvas.tokens.controlled.length > 0) {
|
if (canvas.tokens.controlled.length > 0) {
|
||||||
tokens = canvas.tokens.controlled
|
tokens = canvas.tokens.controlled;
|
||||||
}
|
}
|
||||||
if (tokens.length > 0) {
|
if (tokens.length > 0) {
|
||||||
main(tokens)
|
main(tokens);
|
||||||
} else {
|
} else {
|
||||||
ui.notifications.error('Please select or target a token')
|
ui.notifications.error('Please select or target a token');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(tokens) {
|
async function main(tokens) {
|
||||||
const currencies = ['Copper', 'Silver', 'Gold', 'Platinum']
|
const currencies = ['Copper', 'Silver', 'Gold', 'Platinum'];
|
||||||
let template = '<div><table><thead><tr><th>Actor</th><th>Currency</th></tr></thead><tbody>'
|
let template = '<div><table><thead><tr><th>Actor</th><th>Currency</th></tr></thead><tbody>';
|
||||||
const fmtOptions = {
|
const fmtOptions = {
|
||||||
minimumIntegerDigits: 1,
|
minimumIntegerDigits: 1,
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: 2,
|
||||||
maximumFractionDigits: 2
|
maximumFractionDigits: 2,
|
||||||
}
|
};
|
||||||
const fmt = Intl.NumberFormat('en-US', fmtOptions)
|
const fmt = Intl.NumberFormat('en-US', fmtOptions);
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
const actor = token.actor
|
const actor = token.actor;
|
||||||
let total = 0
|
let total = 0;
|
||||||
for (const item of actor.items.filter(i => currencies.indexOf(i.name) > -1)) {
|
for (const item of actor.items.filter((i) => currencies.indexOf(i.name) > -1)) {
|
||||||
total += item.system.price * item.system.quantity
|
total += item.system.price * item.system.quantity;
|
||||||
}
|
}
|
||||||
template += `<tr><td>${actor.name}</td><td>${fmt.format::(total)}</td></tr>`
|
template += `<tr><td>${actor.name}</td><td>${fmt.format(total)}</td></tr>`;
|
||||||
}
|
}
|
||||||
template += '</thead></tbody>'
|
template += '</thead></tbody>';
|
||||||
Dialog.prompt({
|
foundry.applications.api.DialogV2.prompt({
|
||||||
title: 'Currency Totals',
|
window: { title: 'Currency Totals' },
|
||||||
content: template
|
content: template,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
4
macros/powerMenu.js
Normal file
4
macros/powerMenu.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
game.modules.get('swade-mb-helpers').api.powerEffectsMenu({
|
||||||
|
token,
|
||||||
|
targets: game.user.targets,
|
||||||
|
});
|
||||||
@ -1,5 +1,5 @@
|
|||||||
new Dialog({
|
new foundry.applications.api.DialogV2({
|
||||||
title: 'Damage Roll Configuration',
|
window: { title: "Damage Roll Configuration" },
|
||||||
content: `
|
content: `
|
||||||
<form>
|
<form>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -16,24 +16,28 @@ new Dialog({
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
`,
|
`,
|
||||||
buttons: {
|
buttons: [
|
||||||
ok: {
|
{
|
||||||
label: 'Roll Damage',
|
action: "ok",
|
||||||
callback: (html) => {
|
label: "Roll Damage",
|
||||||
const damageRoll = html.find('input[name="damageRoll"]').val()
|
callback: (event, button, dialog) => {
|
||||||
let flavor = html.find('input[name="flavor"]').val()
|
const form = new foundry.applications.ux.FormDataExtended(button.form);
|
||||||
const ap = parseInt(html.find('input[name="ap"]').val()) || 0
|
console.log(form)
|
||||||
const options = {}
|
const damageRoll = form.object.damageRoll;
|
||||||
|
let flavor = form.object.flavor;
|
||||||
|
const ap = parseInt(form.object.ap) || 0;
|
||||||
|
const options = {};
|
||||||
if (ap > 0) {
|
if (ap > 0) {
|
||||||
flavor = `${flavor ? flavor + ' - ' : ''}AP: ${ap}`
|
flavor = `${flavor ? flavor + " - " : ""}AP: ${ap}`
|
||||||
options.ap = ap
|
options.ap = ap;
|
||||||
}
|
}
|
||||||
// Perform the damage roll and send the message
|
// Perform the damage roll and send the message
|
||||||
new CONFIG.Dice.DamageRoll(damageRoll, null, options).toMessage({ flavor })
|
new CONFIG.Dice.DamageRoll(damageRoll, null, options).toMessage({ flavor });
|
||||||
}
|
|
||||||
},
|
},
|
||||||
cancel: {
|
},
|
||||||
label: 'Cancel'
|
{
|
||||||
}
|
action: "cancel",
|
||||||
}
|
label: "Cancel",
|
||||||
}).render(true)
|
},
|
||||||
|
],
|
||||||
|
}).render(true);
|
||||||
|
|||||||
@ -1,39 +1,42 @@
|
|||||||
const requestFearRollFromTokens = game.modules.get('swade-mb-helpers').api.requestRollFromTokens
|
const requestFearRollFromTokens = game.modules.get('swade-mb-helpers').api.requestFearRollFromTokens;
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let tokens = Array.from(game.user.targets)
|
let tokens = Array.from(game.user.targets);
|
||||||
if (tokens.length < 1) {
|
if (tokens.length < 1) {
|
||||||
tokens = canvas.tokens.controlled
|
tokens = canvas.tokens.controlled;
|
||||||
}
|
}
|
||||||
if (tokens.length < 1) {
|
if (tokens.length < 1) {
|
||||||
ui.notifications.error('Please target or select some tokens')
|
ui.notifications.error('Please target or select some tokens');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuData = {
|
new foundry.applications.api.DialogV2({
|
||||||
inputs: [
|
window: { title: 'Request Fear roll...' },
|
||||||
{ type: 'info', label: `Requesting Fear roll from ${tokens.map(t => t.name).join(', ')}` },
|
content: `
|
||||||
{ type: 'number', label: 'Fear Check Penalty', options: 0 }
|
<form>
|
||||||
],
|
<p>Requesting Fear roll from ${tokens.map((t) => t.name).join(', ')}.</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Fear Check Penalty
|
||||||
|
<input type="number" value="0" name="fear">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{ label: 'Request roll', value: 'ok', default: true },
|
{
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
action: "submit",
|
||||||
]
|
label: 'Request Roll',
|
||||||
}
|
callback: (event, button, dialog) => {
|
||||||
const menuConfig = {
|
formdata = new foundry.applications.ux.FormDataExtended(button.form).object
|
||||||
title: 'Request Fear roll...'
|
const fear = parseInt(formdata.fear) || 0;
|
||||||
}
|
const options = { targetNumber: 4, fear };
|
||||||
const result = await warpgate.menu(menuData, menuConfig)
|
requestFearRollFromTokens(tokens, options);
|
||||||
|
},
|
||||||
if (result.buttons !== 'ok') {
|
},
|
||||||
return
|
{
|
||||||
}
|
action: "cancel", label: 'Cancel',
|
||||||
console.log(result)
|
},
|
||||||
const fear = result.inputs[1] || 0
|
],
|
||||||
const targetNumber = 4
|
}).render(true);
|
||||||
const options = { targetNumber, fear }
|
|
||||||
|
|
||||||
requestFearRollFromTokens(tokens, options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main();
|
||||||
|
|||||||
@ -1,72 +1,95 @@
|
|||||||
const requestRollFromTokens = game.modules.get('swade-mb-helpers').api.requestRollFromTokens
|
const requestRollFromTokens = game.modules.get('swade-mb-helpers').api.requestRollFromTokens;
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let tokens = Array.from(game.user.targets)
|
let tokens = Array.from(game.user.targets);
|
||||||
if (tokens.length < 1) {
|
if (tokens.length < 1) {
|
||||||
tokens = canvas.tokens.controlled
|
tokens = canvas.tokens.controlled;
|
||||||
}
|
}
|
||||||
if (tokens.length < 1) {
|
if (tokens.length < 1) {
|
||||||
ui.notifications.error('Please target or select some tokens')
|
ui.notifications.error('Please target or select some tokens');
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuData = {
|
const attributes = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor'];
|
||||||
inputs: [
|
const skillSet = new Set();
|
||||||
{ type: 'info', label: `Requesting roll from ${tokens.map(t => t.name).join(', ')}` },
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
label: 'Trait to roll',
|
|
||||||
options: []
|
|
||||||
},
|
|
||||||
{ type: 'number', label: 'Roll Modifier', options: 0 },
|
|
||||||
{ type: 'text', label: 'Roll Modifier Description', options: 'Roll Modifier' },
|
|
||||||
{ type: 'number', label: 'Target Number', options: 4 }
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{ label: 'Request roll', value: 'ok', default: true },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const menuConfig = {
|
|
||||||
title: 'Request roll...'
|
|
||||||
}
|
|
||||||
for (const attribute of ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']) {
|
|
||||||
menuData.inputs[1].options.push(
|
|
||||||
{ html: `Attribute | ${attribute}`, value: `a|${attribute}` }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const skillSet = new Set()
|
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
const skills = token.actor.items.filter(i => i.type === 'skill' &&
|
const tokenSkills = token.actor.items.filter(
|
||||||
!['Untrained', 'Unskilled Attempt'].includes(i.name))
|
(i) => i.type === 'skill' && !['Untrained', 'Untrained Attempt'].includes(i.name),
|
||||||
for (const skill of skills) {
|
);
|
||||||
skillSet.add(skill.name)
|
for (const skill of tokenSkills) {
|
||||||
|
skillSet.add(skill.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const skill of Array.from(skillSet).sort()) {
|
const attributeOptions = attributes
|
||||||
menuData.inputs[1].options.push(
|
.map(
|
||||||
{ html: `Skill | ${skill}`, value: `s|${skill}` })
|
(a) => `
|
||||||
}
|
<option ${a === 'Agility' ? 'selected ' : ''} value="a|${a}">${a}</option>`,
|
||||||
menuData.inputs[1].options.push(
|
)
|
||||||
{ html: 'Skill | Untrained', value: 's|NOSKILL' })
|
.join('');
|
||||||
const result = await warpgate.menu(menuData, menuConfig)
|
const skillOptions = Array.from(skillSet)
|
||||||
|
.sort()
|
||||||
if (result.buttons !== 'ok') {
|
.map(
|
||||||
return
|
(s) => `
|
||||||
}
|
<option value="s|${s}">${s}</option>`,
|
||||||
console.log(result)
|
)
|
||||||
const rollMod = result.inputs[2]
|
.join('');
|
||||||
const rollModDesc = result.inputs[3]
|
const content = `
|
||||||
const rollParts = result.inputs[1].split('|')
|
<form>
|
||||||
const rollType = (rollParts[0] === 'a' ? 'attribute' : 'skill')
|
<p>Requesting roll from ${tokens.map((t) => t.name).join(', ')}.</p>
|
||||||
const rollDesc = rollParts[1]
|
<div class="form-group">
|
||||||
const targetNumber = result.inputs[4] || 4
|
<label for="trait">Trait to roll</label>
|
||||||
const options = { targetNumber }
|
<select name="trait">
|
||||||
|
<optgroup label="Attributes">${attributeOptions}</optgroup>
|
||||||
|
<optgroup label="Skills">
|
||||||
|
${skillOptions}
|
||||||
|
<option value="s|NOSKILL">Untrained</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="mod">Roll Modifier:</label>
|
||||||
|
<input type="number" value="0" name="mod">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modDesc">Roll Modifier Description:</label>
|
||||||
|
<input type="text" value="Roll Modifier" name="modDesc">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tn">Target Number</label>
|
||||||
|
<input type="number" value="4" name="tn">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
const buttons = [
|
||||||
|
{
|
||||||
|
action: "submit",
|
||||||
|
label: 'Request Roll',
|
||||||
|
callback: (event, button, dialog) => {
|
||||||
|
const form = button.form;
|
||||||
|
const formDataObject = new foundry.applications.ux.FormDataExtended(form).object;
|
||||||
|
console.log(formDataObject);
|
||||||
|
const rollMod = parseInt(formDataObject.mod);
|
||||||
|
const rollModDesc = formDataObject.modDesc;
|
||||||
|
const rollParts = formDataObject.trait.split('|');
|
||||||
|
const rollType = rollParts[0] === 'a' ? 'attribute' : 'skill';
|
||||||
|
const rollDesc = rollParts[1];
|
||||||
|
const targetNumber = parseInt(formDataObject.tn);
|
||||||
|
const options = { targetNumber };
|
||||||
if (rollMod !== 0) {
|
if (rollMod !== 0) {
|
||||||
options.mods = [{ label: rollModDesc, value: rollMod }]
|
options.mods = [{ label: rollModDesc, value: rollMod }];
|
||||||
|
}
|
||||||
|
requestRollFromTokens(tokens, rollType, rollDesc, options);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: "cancel", label: 'Cancel',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
new foundry.applications.api.DialogV2({
|
||||||
|
window { title: 'Request roll' },
|
||||||
|
content,
|
||||||
|
buttons,
|
||||||
|
}).render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestRollFromTokens(tokens, rollType, rollDesc, options)
|
main();
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
||||||
|
|||||||
37
macros/requestRollMATT.js
Normal file
37
macros/requestRollMATT.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// This will request rolls from the tile's current collection, assuming
|
||||||
|
// that collection is tokens.
|
||||||
|
// call this from MATT's Run Macro with the following arguments,
|
||||||
|
// 1. roll type: "attribute" or "skill" (in double quotes)
|
||||||
|
// 2. roll description: attribute or skill name as you want
|
||||||
|
// it to appear in the request title (in double quotes), eg "Strength"
|
||||||
|
// or "Common Knowledge"
|
||||||
|
// 3... paired arguments, each pair a modifier and a description,
|
||||||
|
// eg: '-2 "Noxious Fog" +1 "Bless Aura"'
|
||||||
|
// so an entire arguments box in MATT may look like this:
|
||||||
|
// "skill" "Common Knowledge" -2 "Ugly Wallpaper" +1 "Rousing Speech"
|
||||||
|
|
||||||
|
const requestRollFromTokens = game.modules.get('swade-mb-helpers').api.requestRollFromTokens
|
||||||
|
const tokens = arguments[0].value.tokens.map(t => canvas.tokens.get(t.id))
|
||||||
|
const rolldata = args
|
||||||
|
|
||||||
|
async function main () {
|
||||||
|
if (tokens.length < 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rollType = rolldata.shift()
|
||||||
|
const rollDesc = rolldata.shift()
|
||||||
|
const options = { targetNumber: 4 }
|
||||||
|
const mods = []
|
||||||
|
while (rolldata.length > 0) {
|
||||||
|
const value = Number(rolldata.shift())
|
||||||
|
const label = rolldata.shift()
|
||||||
|
mods.push({ label, value })
|
||||||
|
}
|
||||||
|
if (mods.length > 0) {
|
||||||
|
options.mods = mods
|
||||||
|
}
|
||||||
|
|
||||||
|
requestRollFromTokens(tokens, rollType, rollDesc, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@ -1,123 +1,156 @@
|
|||||||
const argBright = typeof args === 'undefined' ? null : args.length > 0 ? args[0] : null
|
const argBright = typeof args === 'undefined' ? null : args.length > 0 ? args[0] : null;
|
||||||
// argument can be one of 'bright', 'dim', 'dark', 'pitchdark'. Other values
|
// argument can be one of 'bright', 'dim', 'dark', 'pitchdark'. Other values
|
||||||
// will guess based on scene darkness
|
// will guess based on scene darkness
|
||||||
const BRIGHT_LEVELS = ['bright', 'dim', 'dark', 'pitchdark']
|
const BRIGHT_LEVELS = ['bright', 'dim', 'dark', 'pitchdark'];
|
||||||
const THRESHOLDS = {
|
const THRESHOLDS = {
|
||||||
dim: 0.4,
|
dim: 0.4,
|
||||||
dark: 0.6,
|
dark: 0.6,
|
||||||
pitchdark: 0.8
|
pitchdark: 0.8,
|
||||||
}
|
};
|
||||||
const RANGES = {
|
const RANGES = {
|
||||||
basic: {
|
basic: {
|
||||||
bright: 25,
|
bright: 25,
|
||||||
dim: 25,
|
dim: 25,
|
||||||
dark: 10,
|
dark: 10,
|
||||||
pitchdark: 0
|
pitchdark: 0,
|
||||||
},
|
},
|
||||||
lowlight: {
|
lowlight: {
|
||||||
bright: 25,
|
bright: 25,
|
||||||
dim: 25,
|
dim: 25,
|
||||||
dark: 10,
|
dark: 10,
|
||||||
pitchdark: 0
|
pitchdark: 0,
|
||||||
},
|
},
|
||||||
darkvision: {
|
darkvision: {
|
||||||
bright: 25,
|
bright: 25,
|
||||||
dim: 25,
|
dim: 25,
|
||||||
dark: 10,
|
dark: 10,
|
||||||
pitchdark: 10
|
pitchdark: 10,
|
||||||
},
|
},
|
||||||
nightvision: {
|
nightvision: {
|
||||||
bright: 200,
|
bright: 200,
|
||||||
dim: 200,
|
dim: 200,
|
||||||
dark: 200,
|
dark: 200,
|
||||||
pitchdark: 200
|
pitchdark: 200,
|
||||||
},
|
},
|
||||||
blindsense: {
|
blindsense: {
|
||||||
bright: 5,
|
bright: 5,
|
||||||
dim: 5,
|
dim: 5,
|
||||||
dark: 5,
|
dark: 5,
|
||||||
pitchdark: 5
|
pitchdark: 5,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
const SIGHT_NAMES = {
|
const SIGHT_NAMES = {
|
||||||
lowlight: 'low-light-vision',
|
lowlight: 'low-light-vision',
|
||||||
darkvision: 'darkvision',
|
darkvision: 'darkvision',
|
||||||
nightvision: 'night-vision',
|
nightvision: 'night-vision',
|
||||||
blindsense: 'blindsense'
|
blindsense: 'blindsense',
|
||||||
}
|
};
|
||||||
const SIGHT_MODES = {
|
const SIGHT_MODES = {
|
||||||
lowlight: 'lowlight',
|
lowlight: 'lowlight',
|
||||||
darkvision: 'darkvision',
|
darkvision: 'darkvision',
|
||||||
nightvision: 'darkvision',
|
nightvision: 'darkvision',
|
||||||
basic: 'basic',
|
basic: 'basic',
|
||||||
blindsense: 'blindsense'
|
blindsense: 'blindsense',
|
||||||
}
|
};
|
||||||
|
|
||||||
function findAbility(token, swid) {
|
function findAbility(token, swid) {
|
||||||
return token.actor.items.find(i => i.type === 'ability' && i.system.swid === swid)
|
return token.actor.items.find((i) => i.type === 'ability' && i.system.swid === swid);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const scene = game.scenes.current
|
const scene = game.scenes.current;
|
||||||
let sceneBright = BRIGHT_LEVELS[0]
|
let sceneBright = BRIGHT_LEVELS[0];
|
||||||
if (scene.darkness > THRESHOLDS.pitchdark) {
|
if (scene.darkness > THRESHOLDS.pitchdark) {
|
||||||
sceneBright = BRIGHT_LEVELS[3]
|
sceneBright = BRIGHT_LEVELS[3];
|
||||||
} else if (scene.darkness > THRESHOLDS.dark) {
|
} else if (scene.darkness > THRESHOLDS.dark) {
|
||||||
sceneBright = BRIGHT_LEVELS[2]
|
sceneBright = BRIGHT_LEVELS[2];
|
||||||
} else if (scene.darkness > THRESHOLDS.dim) {
|
} else if (scene.darkness > THRESHOLDS.dim) {
|
||||||
sceneBright = BRIGHT_LEVELS[1]
|
sceneBright = BRIGHT_LEVELS[1];
|
||||||
}
|
}
|
||||||
let bright = sceneBright
|
let bright = sceneBright;
|
||||||
if (argBright && BRIGHT_LEVELS.includes(argBright)) {
|
if (argBright && BRIGHT_LEVELS.includes(argBright)) {
|
||||||
bright = argBright
|
bright = argBright;
|
||||||
}
|
}
|
||||||
const menuData = {
|
|
||||||
inputs: [
|
new foundry.applications.api.DialogV2({
|
||||||
{ type: 'radio', label: 'Bright Light', options: ['bright', bright === BRIGHT_LEVELS[0]] },
|
window: { title: 'Select scene brightness' },
|
||||||
{ type: 'radio', label: 'Dim Light', options: ['bright', bright === BRIGHT_LEVELS[1]] },
|
content: `
|
||||||
{ type: 'radio', label: 'Dark', options: ['bright', bright === BRIGHT_LEVELS[2]] },
|
<form>
|
||||||
{ type: 'radio', label: 'Pitch Dark', options: ['bright', bright === BRIGHT_LEVELS[3]] }
|
<h2>Set token vision</h2>
|
||||||
],
|
<p>All tokens with vision will be adjusted</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="radio" name="bright" value="${BRIGHT_LEVELS[0]}"
|
||||||
|
${bright === BRIGHT_LEVELS[0] ? 'checked' : ''}/>
|
||||||
|
Bright Light
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="radio" name="bright" value="${BRIGHT_LEVELS[1]}"
|
||||||
|
${bright === BRIGHT_LEVELS[1] ? 'checked' : ''}/>
|
||||||
|
Dim Light
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="radio" name="bright" value="${BRIGHT_LEVELS[2]}"
|
||||||
|
${bright === BRIGHT_LEVELS[2] ? 'checked' : ''}/>
|
||||||
|
Dark
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="radio" name="bright" value="${BRIGHT_LEVELS[3]}"
|
||||||
|
${bright === BRIGHT_LEVELS[3] ? 'checked' : ''}/>
|
||||||
|
Pitch Dark
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{ label: 'Select Scene Brightness', value: 'ok', default: true },
|
{
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
action: "submit",
|
||||||
]
|
label: 'Select scene Brightness',
|
||||||
}
|
value: 'ok',
|
||||||
const menuConfig = { title: 'Select scene brightness' }
|
callback: (event, button, dialog) => {
|
||||||
const result = await warpgate.menu(menuData, menuConfig)
|
const form = button.form;
|
||||||
if (result.buttons !== 'ok') { return }
|
const formDataObject = new foundry.applications.ux.FormDataExtended(form).object;
|
||||||
|
console.log('form data', formDataObject, form);
|
||||||
|
|
||||||
for (let i = 0; i < 4; i++) {
|
bright = formDataObject.bright;
|
||||||
if (result.inputs[i]) { bright = BRIGHT_LEVELS[i] }
|
for (const tokenId of scene.tokens.map((t) => t.id)) {
|
||||||
}
|
const token = scene.tokens.get(tokenId);
|
||||||
|
|
||||||
for (const tokenId of scene.tokens.map(t => t.id)) {
|
|
||||||
const token = scene.tokens.get(tokenId)
|
|
||||||
if (!token.sight.enabled) {
|
if (!token.sight.enabled) {
|
||||||
console.log(`Skipping ${token.name}, vision not enabled`)
|
console.log(`Skipping ${token.name}, vision not enabled`);
|
||||||
continue
|
continue;
|
||||||
// don't set sight on a token where it's not enabled
|
// don't set sight on a token where it's not enabled
|
||||||
}
|
}
|
||||||
let sightType = 'basic'
|
let sightType = 'basic';
|
||||||
for (const sight in SIGHT_NAMES) {
|
for (const sight in SIGHT_NAMES) {
|
||||||
if (findAbility(token, SIGHT_NAMES[sight])) {
|
if (findAbility(token, SIGHT_NAMES[sight])) {
|
||||||
sightType = sight
|
sightType = sight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const range = RANGES[sightType][bright]
|
const range = RANGES[sightType][bright];
|
||||||
const sightMode = SIGHT_MODES[sightType]
|
const sightMode = SIGHT_MODES[sightType];
|
||||||
const visionModeData = CONFIG.Canvas.visionModes[sightMode].vision.defaults
|
const visionModeData = CONFIG.Canvas.visionModes[sightMode].vision.defaults;
|
||||||
const data = {
|
const data = {
|
||||||
'sight.range': range,
|
'sight.range': range,
|
||||||
'sight.visionMode': sightMode,
|
'sight.visionMode': sightMode,
|
||||||
'sight.attenuation': visionModeData.attenuation,
|
'sight.attenuation': visionModeData.attenuation,
|
||||||
'sight.brightness': visionModeData.brightness,
|
'sight.brightness': visionModeData.brightness,
|
||||||
'sight.saturation': visionModeData.saturation,
|
'sight.saturation': visionModeData.saturation,
|
||||||
'sight.contrast': visionModeData.contrast
|
'sight.contrast': visionModeData.contrast,
|
||||||
}
|
};
|
||||||
console.log(`Updating ${token.name}:`, sightType, bright, data)
|
console.log(`Updating ${token.name}:`, sightType, bright, data);
|
||||||
await token.update(data)
|
token.update(data);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ action: "cancel", label: 'Cancel' },
|
||||||
|
],
|
||||||
|
}).render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main();
|
||||||
|
|||||||
14722
package-lock.json
generated
14722
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
@ -1,11 +1,53 @@
|
|||||||
{
|
{
|
||||||
|
"private": true,
|
||||||
|
"name": "swade-mb-helpers",
|
||||||
|
"version": "2.4.3",
|
||||||
|
"description": "Mike's Collection of swade helpers",
|
||||||
|
"license": "ALL RIGHTS RESERVED",
|
||||||
|
"homepage": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "Mike"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "gulp build",
|
||||||
|
"build:watch": "gulp watch",
|
||||||
|
"link-project": "gulp link",
|
||||||
|
"clean": "gulp clean",
|
||||||
|
"clean:link": "gulp link --clean",
|
||||||
|
"lint": "eslint --ext .js,.cjs,.mjs .",
|
||||||
|
"lint:fix": "eslint --ext .js,.cjs,.mjs --fix .",
|
||||||
|
"format": "prettier --write \"./**/*.(js|cjs|mjs|json|yml|scss)\"",
|
||||||
|
"postinstall": "husky install"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@league-of-foundry-developers/foundry-vtt-types": "^9.280.0",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"eslint": "^8.48.0",
|
"@rollup/stream": "^3.0.1",
|
||||||
"eslint-config-standard": "^17.1.0",
|
"@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0",
|
||||||
"eslint-plugin-import": "^2.28.1",
|
"eslint": "^9.2.0",
|
||||||
"eslint-plugin-n": "^16.0.2",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-promise": "^6.1.1",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"typescript": "^5.2.2"
|
"fs-extra": "^11.2.0",
|
||||||
|
"gulp": "^5.0.0",
|
||||||
|
"gulp-dart-sass": "^1.1.0",
|
||||||
|
"gulp-sourcemaps": "^2.6.5",
|
||||||
|
"husky": "^9.0.11",
|
||||||
|
"lint-staged": "^16.1.0",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"prettier-eslint": "^16.3.0",
|
||||||
|
"prettier-eslint-cli": "^8.0.1",
|
||||||
|
"rollup": "^2.79.2",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-language-server": "^4.3.3",
|
||||||
|
"vinyl-buffer": "^1.0.1",
|
||||||
|
"vinyl-source-stream": "^2.0.0",
|
||||||
|
"vscode-langservers-extracted": "^4.10.0",
|
||||||
|
"yargs": "^18.0.0"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.(js|cjs|mjs)": "eslint --fix",
|
||||||
|
"*.(json|yml|scss)": "prettier --write"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
2024/01/29-23:43:16.099904 7f72097bd700 Delete type=3 #1
|
|
||||||
2024/01/29-23:43:16.103068 7f6f6bfff700 Level-0 table #5: started
|
|
||||||
2024/01/29-23:43:16.110852 7f6f6bfff700 Level-0 table #5: 15710 bytes OK
|
|
||||||
2024/01/29-23:43:16.121887 7f6f6bfff700 Delete type=0 #3
|
|
||||||
2024/01/29-23:43:16.122061 7f6f6bfff700 Manual compaction at level-0 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items.effects!RC1Nz6iph8wPPK1B.g9W5hJisq3MsCpZW' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
2024/01/29-23:43:17.011739 7fcd23fff700 Delete type=3 #1
|
|
||||||
2024/01/29-23:43:17.014256 7fcd237fe700 Level-0 table #5: started
|
|
||||||
2024/01/29-23:43:17.021608 7fcd237fe700 Level-0 table #5: 6787 bytes OK
|
|
||||||
2024/01/29-23:43:17.030434 7fcd237fe700 Delete type=0 #3
|
|
||||||
2024/01/29-23:43:17.030575 7fcd237fe700 Manual compaction at level-0 from '!items!JWyBQe4tnOYljFAF' @ 72057594037927935 : 1 .. '!items!tWWSfEMmLmws6Yb1' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
2024/01/29-23:43:17.871203 7fcc2b7fe700 Delete type=3 #1
|
|
||||||
2024/01/29-23:43:17.874557 7fcc2a542700 Level-0 table #5: started
|
|
||||||
2024/01/29-23:43:17.881745 7fcc2a542700 Level-0 table #5: 1751 bytes OK
|
|
||||||
2024/01/29-23:43:17.891265 7fcc2a542700 Delete type=0 #3
|
|
||||||
2024/01/29-23:43:17.891403 7fcc2a542700 Manual compaction at level-0 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
2024/01/29-23:43:18.604510 7f21da7be700 Delete type=3 #1
|
|
||||||
2024/01/29-23:43:18.607134 7f21d8fbb700 Level-0 table #5: started
|
|
||||||
2024/01/29-23:43:18.630768 7f21d8fbb700 Level-0 table #5: 18825 bytes OK
|
|
||||||
2024/01/29-23:43:18.639775 7f21d8fbb700 Delete type=0 #3
|
|
||||||
2024/01/29-23:43:18.639913 7f21d8fbb700 Manual compaction at level-0 from '!folders!A3iVDJD2cTuTLpBu' @ 72057594037927935 : 1 .. '!macros!wU2mAUnw3RW9qMT8' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Blind",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/svg/blind.svg",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Blind'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1678165762773,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "e9HvLMtaDw2qpcE8",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 700000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!e9HvLMtaDw2qpcE8"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Boost/Lower Trait",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/life/cross-embers-glow-yellow-purple.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Boost/Lower Trait'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677458254287,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "1AqIFHAcX5TRdE8X",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 2100000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!1AqIFHAcX5TRdE8X"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Burrow",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/magic/earth/projectile-stone-landslide.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Burrow'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1696179835774,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "e4LvHlTNDy5zcGIG",
|
|
||||||
"sort": 400000,
|
|
||||||
"_key": "!macros!e4LvHlTNDy5zcGIG"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Confusion",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/control/hypnosis-mesmerism-swirl.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Confusion'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1678082334572,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "L2IstecV7ivcrgUI",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 2000000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!L2IstecV7ivcrgUI"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Darksight",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/magic/perception/eye-ringed-glow-angry-small-teal.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Darksight'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1696179835774,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "YIdF96EfItR641oP",
|
|
||||||
"sort": 300000,
|
|
||||||
"_key": "!macros!YIdF96EfItR641oP"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Deflection",
|
|
||||||
"type": "script",
|
|
||||||
"scope": "global",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/defensive/shield-barrier-deflect-teal.webp",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Deflection'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677645552357,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "HYTiftQW0pwwOQGH",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1900000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!HYTiftQW0pwwOQGH"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Detect/Conceal Arcana",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/magic/perception/third-eye-blue-red.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Detect/Conceal Arcana'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1696208623170,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "stHHxnYfGW0X1l5R",
|
|
||||||
"sort": 600000,
|
|
||||||
"_key": "!macros!stHHxnYfGW0X1l5R"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Disguise",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/skills/social/diplomacy-peace-alliance.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Disguise'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1696208623170,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "MYfrVZpLSrpp0vYW",
|
|
||||||
"sort": 200000,
|
|
||||||
"_key": "!macros!MYfrVZpLSrpp0vYW"
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Effect Macros",
|
|
||||||
"sorting": "a",
|
|
||||||
"folder": null,
|
|
||||||
"type": "Macro",
|
|
||||||
"_id": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 0,
|
|
||||||
"color": null,
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.2.2",
|
|
||||||
"coreVersion": "11.315",
|
|
||||||
"createdTime": 1695251622720,
|
|
||||||
"modifiedTime": 1700436446749,
|
|
||||||
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
|
|
||||||
},
|
|
||||||
"_key": "!folders!hIbrWxg1nDutCSwt"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Entangle",
|
|
||||||
"type": "script",
|
|
||||||
"scope": "global",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/nature/root-vine-barrier-wall-brown.webp",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Entangle'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1678164427219,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "2TOeRNCJT3T2px8D",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1800000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!2TOeRNCJT3T2px8D"
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Havoc",
|
|
||||||
"type": "script",
|
|
||||||
"scope": "global",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/air/air-burst-spiral-yellow.webp",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Havoc'\n})",
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.2.2",
|
|
||||||
"coreVersion": "11.315",
|
|
||||||
"createdTime": 1678164427219,
|
|
||||||
"modifiedTime": 1700436564699,
|
|
||||||
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
|
|
||||||
},
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_id": "0CalvjuxtMvY2enn",
|
|
||||||
"sort": 0,
|
|
||||||
"_key": "!macros!0CalvjuxtMvY2enn"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Intangibility",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Intangibility'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1678168528898,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "OMDjgWLJyE9BJAwT",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1700000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!OMDjgWLJyE9BJAwT"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Invisibility",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/svg/invisible.svg",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Invisibility'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1678168163811,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "11GOryzx2Q8MXbT6",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1600000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!11GOryzx2Q8MXbT6"
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Protection",
|
|
||||||
"type": "script",
|
|
||||||
"scope": "global",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "systems/swade/assets/icons/status/status_protection.svg",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Protection'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677630174987,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "YETsNWOWfIxyLPdC",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1500000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!YETsNWOWfIxyLPdC"
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"name": "Shape Change",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "8gxeYSUJ1FQhmJRw",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/magic/symbols/runes-star-blue.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Shape Change'\n})",
|
|
||||||
"sort": 100000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1695618313958,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_key": "!macros!8gxeYSUJ1FQhmJRw"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Sloth/Speed",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/skills/movement/feet-winged-sandals-tan.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Sloth/Speed'\n})",
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677996503821,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"_id": "uWF4I2mnDkV8NZ6j",
|
|
||||||
"sort": 500000,
|
|
||||||
"_key": "!macros!uWF4I2mnDkV8NZ6j"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Smite",
|
|
||||||
"type": "script",
|
|
||||||
"scope": "global",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "systems/swade/assets/icons/status/status_smite.svg",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Smite'\n})",
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677548889704,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "C1vGk7AKQDpcvKyP",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1400000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!C1vGk7AKQDpcvKyP"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Ally",
|
|
||||||
"type": "script",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/runes-star-orange.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Ally'\n})",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1677996503821,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"_id": "RV09eJi9iG5bfupo",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"sort": 1000000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!RV09eJi9iG5bfupo"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Animal",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "745gcs8ytsCLPXe1",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/mask-yellow-orange.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Animal'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1694403406793,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"sort": 800000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!745gcs8ytsCLPXe1"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Monster",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "V8r5hugGBQfqlhYt",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/mask-metal-silver-white.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Monster'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1694403446899,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"sort": 1300000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!V8r5hugGBQfqlhYt"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Nature's Ally",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "wU2mAUnw3RW9qMT8",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/mask-yellow-orange.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Nature's Ally'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1694403243667,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"sort": 900000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!wU2mAUnw3RW9qMT8"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Planar Ally",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "jACgJo0HAmkyzFjZ",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/runes-star-orange-purple.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Planar Ally'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1694403168266,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"sort": 1200000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!jACgJo0HAmkyzFjZ"
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Summon Undead",
|
|
||||||
"type": "script",
|
|
||||||
"_id": "eeX3Hoy2Uxo5BeUC",
|
|
||||||
"author": "R9ZgY0IvWl8ovIuT",
|
|
||||||
"img": "icons/magic/symbols/star-yellow.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Summon Undead'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.1.4",
|
|
||||||
"coreVersion": "11.311",
|
|
||||||
"createdTime": 1694404089533,
|
|
||||||
"modifiedTime": 1696209757148,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"sort": 1100000,
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"R9ZgY0IvWl8ovIuT": 3
|
|
||||||
},
|
|
||||||
"_key": "!macros!eeX3Hoy2Uxo5BeUC"
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Zombie",
|
|
||||||
"type": "script",
|
|
||||||
"author": "sVoCvBU1knmXzoYe",
|
|
||||||
"img": "icons/magic/death/hand-dirt-undead-zombie.webp",
|
|
||||||
"scope": "global",
|
|
||||||
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Zombie'\n})",
|
|
||||||
"folder": "hIbrWxg1nDutCSwt",
|
|
||||||
"flags": {
|
|
||||||
"core": {}
|
|
||||||
},
|
|
||||||
"_stats": {
|
|
||||||
"systemId": "swade",
|
|
||||||
"systemVersion": "3.2.5",
|
|
||||||
"coreVersion": "11.315",
|
|
||||||
"createdTime": 1694404089533,
|
|
||||||
"modifiedTime": 1702859394353,
|
|
||||||
"lastModifiedBy": "sVoCvBU1knmXzoYe"
|
|
||||||
},
|
|
||||||
"ownership": {
|
|
||||||
"default": 0,
|
|
||||||
"sVoCvBU1knmXzoYe": 3
|
|
||||||
},
|
|
||||||
"_id": "mdci1DSM3UTaLzrb",
|
|
||||||
"sort": 0,
|
|
||||||
"_key": "!macros!mdci1DSM3UTaLzrb"
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
2024/01/29-23:43:19.357120 7f7d00fbc700 Delete type=3 #1
|
|
||||||
2024/01/29-23:43:19.359763 7f7a63fff700 Level-0 table #5: started
|
|
||||||
2024/01/29-23:43:19.367310 7f7a63fff700 Level-0 table #5: 16288 bytes OK
|
|
||||||
2024/01/29-23:43:19.376324 7f7a63fff700 Delete type=0 #3
|
|
||||||
2024/01/29-23:43:19.376468 7f7a63fff700 Manual compaction at level-0 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!w4TImRTAiNiqDWeL.vQhO6BVdKZOubTUQ' @ 0 : 0; will stop at (end)
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
80
release.sh
Executable file
80
release.sh
Executable file
@ -0,0 +1,80 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
curdir=$(realpath $(dirname $0))
|
||||||
|
version=$1
|
||||||
|
echo $version
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
echo "must give a version specification."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Tagging git release...
|
||||||
|
git tag -a -m $version $version
|
||||||
|
git push origin ${version}
|
||||||
|
echo Tagged git release $(git describe)
|
||||||
|
|
||||||
|
read -r -d '' release_body <<EOF
|
||||||
|
{
|
||||||
|
"body": "${version}",
|
||||||
|
"draft": true,
|
||||||
|
"name": "${version}",
|
||||||
|
"prerelease": false,
|
||||||
|
"tag_name": "${version}",
|
||||||
|
"target_commitish": "${version}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
releaseId=$(curl -n -X 'POST' \
|
||||||
|
'https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases' \
|
||||||
|
-H 'accept: application/json' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "${release_body}" \
|
||||||
|
| jq '.id')
|
||||||
|
|
||||||
|
(cd $curdir/dist; rm -f swade-mb-helpers.zip; zip -r swade-mb-helpers.zip swade-mb-helpers)
|
||||||
|
|
||||||
|
# curl -n -X 'POST' \
|
||||||
|
# "https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases/${releaseId}/assets?name=module.json" \
|
||||||
|
# -H 'accept: application/json' \
|
||||||
|
# -T ./dist/swade-mb-helpers/module.json
|
||||||
|
|
||||||
|
# curl -n -X 'POST' \
|
||||||
|
# "https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases/${releaseId}/assets?name=swade-mb-helpers.zip" \
|
||||||
|
# -H 'accept: application/json' \
|
||||||
|
# -T ./dist/swade-mb-helpers.zip
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Updating module.json"
|
||||||
|
curl -n -X 'POST' \
|
||||||
|
"https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases/${releaseId}/assets?name=module.json" \
|
||||||
|
-H 'accept: application/json' \
|
||||||
|
-H 'Content-Type: multipart/form-data' \
|
||||||
|
-F 'attachment=@dist/swade-mb-helpers/module.json;type=application/json'
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Updating swade-mb-helpers.zip"
|
||||||
|
curl -n -X 'POST' \
|
||||||
|
"https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases/${releaseId}/assets?name=swade-mb-helpers.zip" \
|
||||||
|
-H 'accept: application/json' \
|
||||||
|
-H 'Content-Type: multipart/form-data' \
|
||||||
|
-F 'attachment=@dist/swade-mb-helpers.zip;type=application/x-zip-compressed'
|
||||||
|
|
||||||
|
|
||||||
|
read -r -d '' patch_body <<EOF
|
||||||
|
{
|
||||||
|
"body": "${version}",
|
||||||
|
"draft": false,
|
||||||
|
"name": "${version}",
|
||||||
|
"prerelease": false,
|
||||||
|
"tag_name": "${version}",
|
||||||
|
"target_commitish": "${version}"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "setting to not draft"
|
||||||
|
curl -n -X 'PATCH' \
|
||||||
|
"https://git.bloy.org/api/v1/repos/foundryvtt/swade-mb-helpers/releases/${releaseId}" \
|
||||||
|
-H 'accept: application/json' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d "${patch_body}" \
|
||||||
16
rollup.config.mjs
Normal file
16
rollup.config.mjs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||||
|
// SPDX-FileCopyrightText: 2022 David Archibald
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||||
|
|
||||||
|
export default () => ({
|
||||||
|
input: 'src/module/swade-mb-helpers.js',
|
||||||
|
output: {
|
||||||
|
dir: 'dist/swade-mb-helpers/module',
|
||||||
|
format: 'es',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
plugins: [nodeResolve()],
|
||||||
|
});
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { log, shim } from './shim.js'
|
|
||||||
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
|
|
||||||
import { powerEffects } from './powerEffects.js'
|
|
||||||
|
|
||||||
export class api {
|
|
||||||
static registerFunctions () {
|
|
||||||
log('SWADE MB Helpers initialized')
|
|
||||||
api.globals()
|
|
||||||
}
|
|
||||||
|
|
||||||
static globals () {
|
|
||||||
const moduleName = 'swade-mb-helpers'
|
|
||||||
game.modules.get(moduleName).api = {
|
|
||||||
rulesVersion: shim.rulesVersion,
|
|
||||||
fearTable: shim.fearTableHelper,
|
|
||||||
powerEffects,
|
|
||||||
requestRollFromTokens,
|
|
||||||
requestFearRollFromTokens
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
import { shim } from './shim.js'
|
|
||||||
|
|
||||||
export async function requestFearRollFromTokens (tokens, options = {}) {
|
|
||||||
// tokens: list of tokens to request the roll from
|
|
||||||
// options:
|
|
||||||
// title: tile for the roll dialog. Will have "- {{ token name }}" appended
|
|
||||||
// flavour: flavor text for the roll card. Defaults to title
|
|
||||||
// fear: value of the fear modifier. Defaults to 0. Positive number.
|
|
||||||
const requestingUser = shim.user
|
|
||||||
const title = options?.title || `${requestingUser.name} requests a Fear check`
|
|
||||||
const flavour = options?.flavour || options?.flavor || title
|
|
||||||
const fear = options.fear || 0
|
|
||||||
const rollOpts = {
|
|
||||||
title,
|
|
||||||
flavour,
|
|
||||||
mods: [
|
|
||||||
{ label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function requestRollFromTokens (tokens, rollType, rollDesc, options = {}) {
|
|
||||||
// tokens: list of tokens to request a roll from
|
|
||||||
// rollType: 'attribute' or 'skill
|
|
||||||
// rollDesc: name of attribute or skill
|
|
||||||
// options:
|
|
||||||
// title: title for the roll dialog. Will have "- {{ token name }}"
|
|
||||||
// appended
|
|
||||||
// flavour: flavor text for the roll card. Defaults to title
|
|
||||||
// targetNumber: defaults to 4
|
|
||||||
// mods: list of modifiers {label: "", value: 0, ignore: false}
|
|
||||||
// modCallback: callback function that takes a token and returns a list of
|
|
||||||
// modifiers in the same format as modifiers, above
|
|
||||||
const requestingUser = shim.user
|
|
||||||
const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll`
|
|
||||||
const flavour = options?.flavour || options?.flavor || title
|
|
||||||
const targetNumber = options?.targetNumber || 4
|
|
||||||
const promises = []
|
|
||||||
for (const token of tokens) {
|
|
||||||
const owner = shim.warpgateUtil.firstOwner(token.document)
|
|
||||||
const rollOpts = {
|
|
||||||
title: `${title} - ${token.name}`,
|
|
||||||
targetNumber,
|
|
||||||
flavour
|
|
||||||
}
|
|
||||||
const additionalMods = []
|
|
||||||
if ('mods' in options) {
|
|
||||||
for (const mod of options.mods) {
|
|
||||||
additionalMods.push(mod)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ('modCallback' in options) {
|
|
||||||
const tokenMods = await options.modCallback(token)
|
|
||||||
for (const tm of tokenMods) {
|
|
||||||
additionalMods.push(tm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (additionalMods.length > 0) {
|
|
||||||
rollOpts.additionalMods = additionalMods
|
|
||||||
}
|
|
||||||
promises.push(shim.socket.executeAsUser(requestTokenRoll, owner.id,
|
|
||||||
token.scene.id, token.id, rollType, rollDesc, rollOpts))
|
|
||||||
}
|
|
||||||
const results = (await Promise.allSettled(promises)).map(r => r.value)
|
|
||||||
const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}`
|
|
||||||
const messageData = {
|
|
||||||
flavor: flavour,
|
|
||||||
speaker: { alias: 'Requested Roll Results' },
|
|
||||||
whisper: [...shim.ChatMessage.getWhisperRecipients('GM'), requestingUser],
|
|
||||||
content: `<p>Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:</p>
|
|
||||||
<table><thead><tr><th>Token</th><th>Roll</th><th>Result</th></tr></thead><tbody>`,
|
|
||||||
rolls: []
|
|
||||||
}
|
|
||||||
for (const result of results) {
|
|
||||||
const token = shim.game.scenes.get(result.sceneId).tokens.get(result.tokenId)
|
|
||||||
const roll = (
|
|
||||||
result.result instanceof shim.CONFIG.Dice.SwadeRoll
|
|
||||||
? result.result
|
|
||||||
: shim.CONFIG.Dice[result.result.class].fromData(result.result)
|
|
||||||
)
|
|
||||||
roll.targetNumber = targetNumber
|
|
||||||
let textResult = ''
|
|
||||||
if (roll.successes === -1) {
|
|
||||||
textResult = 'CRITICAL FAILURE'
|
|
||||||
} else if (roll.successes === 0) {
|
|
||||||
textResult = 'failed'
|
|
||||||
} else if (roll.successes === 1) {
|
|
||||||
textResult = 'success'
|
|
||||||
} else {
|
|
||||||
textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}`
|
|
||||||
}
|
|
||||||
messageData.content += ('<tr>' +
|
|
||||||
`<th>${token.name}</th>` +
|
|
||||||
`<td>${roll ? roll.total : '<i>Canceled</i>'}</td>` +
|
|
||||||
`<td>${textResult}</td>` +
|
|
||||||
'</tr>')
|
|
||||||
if (roll) {
|
|
||||||
messageData.rolls.unshift(roll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
messageData.content += '</tbody></table>'
|
|
||||||
shim.ChatMessage.create(messageData, {})
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function requestTokenRoll (
|
|
||||||
sceneId, tokenId, rollType, rollDesc, options
|
|
||||||
) {
|
|
||||||
const scene = shim.scenes.get(sceneId)
|
|
||||||
const token = scene.tokens.get(tokenId)
|
|
||||||
let rollFunc = 'rollAttribute'
|
|
||||||
let rollId = rollDesc.toLowerCase()
|
|
||||||
if (rollType === 'skill') {
|
|
||||||
rollFunc = 'rollSkill'
|
|
||||||
rollId = token.actor.items.filter(i => i.type === 'skill').find(i => (
|
|
||||||
i.system.swid === rollDesc.toLowerCase() ||
|
|
||||||
i.name.toLowerCase() === rollDesc.toLowerCase()))?.id
|
|
||||||
}
|
|
||||||
const result = await token.actor[rollFunc](rollId, options)
|
|
||||||
return { sceneId, tokenId, result }
|
|
||||||
}
|
|
||||||
@ -1,1657 +0,0 @@
|
|||||||
import { CONST, log, shim } from './shim.js'
|
|
||||||
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
|
|
||||||
|
|
||||||
class PowerEffect {
|
|
||||||
constructor (token, targets) {
|
|
||||||
this.token = token
|
|
||||||
this.targets = targets
|
|
||||||
this.effectDocs = []
|
|
||||||
this.menuData = {
|
|
||||||
inputs: [
|
|
||||||
{ type: 'header', label: `${this.name} Effect` },
|
|
||||||
{ type: 'info', label: `Apply ${this.name} Effect` },
|
|
||||||
{ type: 'header', label: 'Global Modifiers' },
|
|
||||||
{ type: 'checkbox', label: 'Glow (+1)' },
|
|
||||||
{ type: 'checkbox', label: 'Shroud (+1)' },
|
|
||||||
{ type: 'checkbox', label: 'Hinder (+1)' },
|
|
||||||
{ type: 'checkbox', label: 'Hurry (+1)' },
|
|
||||||
{ type: 'header', label: '---------------' }
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{ label: 'Apply', value: 'apply' },
|
|
||||||
{ label: 'Apply with Raise', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
this.menuOptions = {
|
|
||||||
title: `${this.name} Effect`,
|
|
||||||
defaultButton: 'Cancel',
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
this.inputs = []
|
|
||||||
this.buttons = null
|
|
||||||
}
|
|
||||||
|
|
||||||
get name () {
|
|
||||||
return 'Unknown Power'
|
|
||||||
}
|
|
||||||
|
|
||||||
get durationRounds () {
|
|
||||||
return this.baseDurationRounds
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async powerEffect () {
|
|
||||||
try {
|
|
||||||
await this.prepMenu()
|
|
||||||
} catch (e) {
|
|
||||||
log('Error preparing menu for power effect: ' + e.toString())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { buttons, inputs } = await shim.warpgateMenu(
|
|
||||||
this.menuData, this.menuOptions)
|
|
||||||
this.buttons = buttons
|
|
||||||
this.inputs = inputs
|
|
||||||
if (this.buttons && this.buttons !== 'cancel') {
|
|
||||||
this.globalModifierEffects()
|
|
||||||
await this.prepResult()
|
|
||||||
await this.applyResult()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
for (const target of this.targets) {
|
|
||||||
shim.applyActiveEffects(target, this.effectDocs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static modEffectDoc (icon, name, key, value, durationRounds) {
|
|
||||||
return shim.createEffectDocument(icon, name, durationRounds, [
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
|
|
||||||
value,
|
|
||||||
priority: 0
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
static glow (durationRounds) {
|
|
||||||
return PowerEffect.modEffectDoc(
|
|
||||||
'icons/magic/light/orb-shadow-blue.webp',
|
|
||||||
'Glow', '@Skill{Stealth}[system.die.modifier]', -2, durationRounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
static shroud (durationRounds) {
|
|
||||||
return PowerEffect.modEffectDoc(
|
|
||||||
'icons/magic/perception/shadow-stealth-eyes-purple.webp',
|
|
||||||
'Shroud', '@Skill{Stealth}[system.die.modifier]', 1, durationRounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
static hinder (durationRounds) {
|
|
||||||
return PowerEffect.modEffectDoc(
|
|
||||||
'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
|
||||||
'Hinder', 'system.stats.speed.value', -2, durationRounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
static hurry (durationRounds) {
|
|
||||||
return PowerEffect.modEffectDoc(
|
|
||||||
'icons/skills/movement/feet-winged-sandals-tan.webp',
|
|
||||||
'Hurry', 'system.stats.speed.value', 2, durationRounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
globalModifierEffects () {
|
|
||||||
this.inputIndex = 8
|
|
||||||
if (this.inputs[3]) { // glow
|
|
||||||
this.effectDocs.push(PowerEffect.glow(this.durationRounds))
|
|
||||||
}
|
|
||||||
if (this.inputs[4]) { // shroud
|
|
||||||
this.effectDocs.push(PowerEffect.shroud(this.durationRounds))
|
|
||||||
}
|
|
||||||
if (this.inputs[5]) { // hinder
|
|
||||||
this.effectDocs.push(PowerEffect.hinder(this.durationRounds))
|
|
||||||
}
|
|
||||||
if (this.inputs[6]) { // hurry
|
|
||||||
this.effectDocs.push(PowerEffect.hurry(this.durationRounds))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class TargetedPowerEffect extends PowerEffect {
|
|
||||||
constructor (token, targets) {
|
|
||||||
super(token, targets)
|
|
||||||
const targetList = this.targets.map(t => t.name).join(', ')
|
|
||||||
this.menuData.inputs[1] = {
|
|
||||||
type: 'info',
|
|
||||||
label: `Apply ${this.name} Effect to ${targetList}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async powerEffect () {
|
|
||||||
if (this.targets.length < 1) {
|
|
||||||
shim.notifications.error(`No target selected for ${this.name}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
super.powerEffect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LingeringDamagePowerEffect extends TargetedPowerEffect {
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.splice(this.menuData.inputs.length - 1, 0, {
|
|
||||||
type: 'checkbox', label: 'Lingering Damage (+2)'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
globalModifierEffects () {
|
|
||||||
super.globalModifierEffects()
|
|
||||||
this.inputIndex += 1
|
|
||||||
if (this.inputs[7]) { // lingering damage
|
|
||||||
const doc = shim.createEffectDocument(
|
|
||||||
'icons/magic/death/skull-poison-green.webp',
|
|
||||||
`Lingering Damage (${this.name})`,
|
|
||||||
1
|
|
||||||
)
|
|
||||||
doc.flags.swade.expiration = CONST.SWADE.STATUS_EFFECT_EXPIRATION.StartOfTurnPrompt
|
|
||||||
this.effectDocs.push(doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArcaneProtectionEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Arcane Protection'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push(
|
|
||||||
{ type: 'checkbox', label: 'Greater', options: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const greater = !!this.inputs[this.inputIndex]
|
|
||||||
const raise = this.buttons === 'raise'
|
|
||||||
const amount = (raise ? -4 : -2) + (greater ? -2 : 0)
|
|
||||||
const icon = 'icons/magic/defensive/shield-barrier-flaming-pentagon-blue.webp'
|
|
||||||
const name = `${greater ? 'Greater ' : ''}Arcane Protection (${raise ? 'major, ' : ''}${amount})`
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(icon, name, this.durationRounds, []))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BlastEffect extends LingeringDamagePowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Blast'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BlindEffect extends TargetedPowerEffect {
|
|
||||||
async prepMenu (token, targets) {
|
|
||||||
this.menuData.inputs.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Strong (+1 point)',
|
|
||||||
options: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get name () {
|
|
||||||
return 'Blind'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const strong = !!this.inputs[this.inputIndex]
|
|
||||||
const icon = 'icons/skills/wounds/injury-eyes-blood-red.webp'
|
|
||||||
const changes = [
|
|
||||||
{
|
|
||||||
key: 'system.stats.globalMods.trait',
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
|
|
||||||
value: '-2',
|
|
||||||
priority: 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
icon, `minor Blindness (Vigor ${strong ? '-2 ' : ''}ends)`,
|
|
||||||
this.durationRounds, changes))
|
|
||||||
if (raise) {
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
icon, `major Blindness (Vigor ${strong ? '-2 ' : ''}ends)`,
|
|
||||||
this.durationRounds, changes)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BurrowEffect extends TargetedPowerEffect {
|
|
||||||
get name () { return 'Burrow' }
|
|
||||||
get baseDurationRounds () { return 5 }
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const icon = 'icons/magic/earth/projectile-stone-landslide.webp'
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
icon,
|
|
||||||
`${raise ? 'major' : 'minor'} ${this.name}`,
|
|
||||||
this.durationRounds,
|
|
||||||
[])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoltEffect extends LingeringDamagePowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Bolt'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoostLowerTraitEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Boost/Lower Trait'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (!this.inputs) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if (this.inputs[this.inputs.length - 4]) { // Boost
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
return 1 // Lower
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
let traitOptions = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
|
|
||||||
const allSkills = []
|
|
||||||
const traits = {}
|
|
||||||
for (const traitName of traitOptions) {
|
|
||||||
const lower = traitName.toLowerCase()
|
|
||||||
traits[traitName] = {
|
|
||||||
name: traitName,
|
|
||||||
type: 'attribute',
|
|
||||||
modkey: `system.attributes.${lower}.die.modifier`,
|
|
||||||
diekey: `system.attributes.${lower}.die.sides`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const token of this.targets) {
|
|
||||||
const skills = token.actor.items.filter(item => item.type === 'skill')
|
|
||||||
for (const skill of skills) {
|
|
||||||
const name = skill.name
|
|
||||||
traits[name] = {
|
|
||||||
name,
|
|
||||||
type: 'skill',
|
|
||||||
modkey: `@Skill{${name}}[system.die.modifier]`,
|
|
||||||
diekey: `@Skill{${name}}[system.die.sides]`
|
|
||||||
}
|
|
||||||
if (name !== 'Unskilled' && !allSkills.find(v => v === name)) {
|
|
||||||
allSkills.push(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
traitOptions = traitOptions.concat(allSkills.sort())
|
|
||||||
}
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat(
|
|
||||||
{ type: 'select', label: 'Trait', options: traitOptions },
|
|
||||||
{ type: 'info', label: 'Boost or Lower?' },
|
|
||||||
{ type: 'radio', label: 'Boost', options: ['isBoost', true] },
|
|
||||||
{ type: 'radio', label: 'Lower', options: ['isBoost', false] },
|
|
||||||
{ type: 'checkbox', label: 'Greater', options: false },
|
|
||||||
{ type: 'checkbox', label: 'Strong (lower only)', options: false }
|
|
||||||
)
|
|
||||||
this.traits = traits
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const direction = this.inputs[this.inputs.length - 4] ? 'Boost' : 'Lower'
|
|
||||||
const durationRounds = (direction === 'Boost' ? 5 : 1)
|
|
||||||
const icon = (direction === 'Boost'
|
|
||||||
? 'icons/magic/life/cross-embers-glow-yellow-purple.webp'
|
|
||||||
: 'icons/magic/movement/chevrons-down-yellow.webp')
|
|
||||||
const trait = this.traits[this.inputs[this.inputIndex]]
|
|
||||||
const greater = !!this.inputs[this.inputIndex + 4]
|
|
||||||
const strong = !!this.inputs[this.inputIndex + 5]
|
|
||||||
|
|
||||||
let namePart = `${direction} ${trait.name}`
|
|
||||||
const mods = []
|
|
||||||
if (direction === 'Lower') {
|
|
||||||
mods.push(`Spirit${strong ? '-2' : ''} ends`)
|
|
||||||
}
|
|
||||||
if (greater) {
|
|
||||||
mods.push('greater')
|
|
||||||
}
|
|
||||||
if (mods.length > 0) {
|
|
||||||
namePart = `${namePart} (${mods.join(', ')})`
|
|
||||||
}
|
|
||||||
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
|
|
||||||
const modValue = (direction === 'Boost' ? '+2' : '-2')
|
|
||||||
const minorEffect = shim.createEffectDocument(
|
|
||||||
icon, `minor ${namePart}`, durationRounds, [
|
|
||||||
{ key: trait.diekey, mode, value: modValue, priority: 0 }
|
|
||||||
])
|
|
||||||
if (direction === 'Lower' && greater) {
|
|
||||||
minorEffect.changes.push({ key: trait.modkey, mode, value: modValue, priority: 0 })
|
|
||||||
}
|
|
||||||
const majorEffect = shim.createEffectDocument(
|
|
||||||
icon, `major ${namePart}`, durationRounds, [
|
|
||||||
{ key: trait.diekey, mode, value: modValue, priority: 0 }
|
|
||||||
])
|
|
||||||
this.effectDocs.push(minorEffect)
|
|
||||||
if (raise) { this.effectDocs.push(majorEffect) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BurstEffect extends LingeringDamagePowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Burst'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConfusionEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Confusion'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push(
|
|
||||||
{ type: 'checkbox', label: 'Greater (adds Shaken)', options: false })
|
|
||||||
this.menuData.buttons = [
|
|
||||||
{ label: 'Distracted', value: 'distracted' },
|
|
||||||
{ label: 'Vulnerable', value: 'vulnerable' },
|
|
||||||
{ label: 'Raise (both)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const greater = !!this.inputs[this.inputIndex]
|
|
||||||
if (this.buttons === 'distracted' || this.buttons === 'raise') {
|
|
||||||
this.effectDocs.push(shim.getStatus('SWADE.Distr', 'Distracted'))
|
|
||||||
}
|
|
||||||
if (this.buttons === 'vulnerable' || this.buttons === 'raise') {
|
|
||||||
this.effectDocs.push(shim.getStatus('SWADE.Vuln', 'Vulnerable'))
|
|
||||||
}
|
|
||||||
if (greater) {
|
|
||||||
this.effectDocs.push(shim.getStatus('SWADE.Shaken', 'Shaken'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DarksightEffect extends TargetedPowerEffect {
|
|
||||||
get name () { return 'Darksight' }
|
|
||||||
get baseDurationRounds () { return 600 }
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push(
|
|
||||||
{ type: 'checkbox', label: '⭐ Greater (+2)', options: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = this.buttons === 'raise'
|
|
||||||
const greater = !!this.inputs[this.inputIndex]
|
|
||||||
const icon = 'icons/magic/perception/eye-ringed-glow-angry-small-teal.webp'
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
icon,
|
|
||||||
`${raise ? 'major' : 'minor'} ${this.name}${greater ? ' (greater)' : ''}`,
|
|
||||||
this.durationRounds,
|
|
||||||
[])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DisguiseEffect extends TargetedPowerEffect {
|
|
||||||
get name () { return 'Disguise' }
|
|
||||||
get baseDurationRounds () { return 100 }
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = this.buttons === 'raise'
|
|
||||||
const icon = 'icons/skills/social/diplomacy-peace-alliance.webp'
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
icon,
|
|
||||||
`${raise ? 'major' : 'minor'} ${this.name}`,
|
|
||||||
this.durationRounds,
|
|
||||||
[])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeflectionEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Deflection'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.buttons = [
|
|
||||||
{ label: 'Melee', value: 'melee' },
|
|
||||||
{ label: 'Ranged', value: 'ranged' },
|
|
||||||
{ label: 'Raise (both)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const effectName = `Deflection (${this.buttons === 'raise' ? 'all' : this.buttons})`
|
|
||||||
const icon = 'icons/magic/defensive/shield-barrier-deflect-teal.webp'
|
|
||||||
this.effectDocs.push(shim.createEffectDocument(icon, effectName, this.durationRounds))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DetectConcealArcanaEffect extends TargetedPowerEffect {
|
|
||||||
get name () { return 'Detect/Conceal Arcana' }
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (this.inputs?.[this.inputIndex + 2] === true) {
|
|
||||||
return 600
|
|
||||||
}
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat(
|
|
||||||
{ type: 'info', label: 'Detect or Conceal?' },
|
|
||||||
{ type: 'radio', label: 'Detect', options: ['isDetect', true] },
|
|
||||||
{ type: 'radio', label: 'Conceal', options: ['isDetect', false] },
|
|
||||||
{ type: 'checkbox', label: 'Strong (+1, conceal only)', options: false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const isDetect = this.inputs[this.inputIndex + 1] === true
|
|
||||||
const strong = !isDetect && !!this.inputs[this.inputIndex + 3]
|
|
||||||
const icon = (isDetect
|
|
||||||
? 'icons/magic/perception/third-eye-blue-red.webp'
|
|
||||||
: 'icons/magic/perception/silhouette-stealth-shadow.webp')
|
|
||||||
const name = `${raise ? 'major ' : ''}${isDetect ? 'Detect' : 'Conceal'} Arcana${strong ? ' (strong)' : ''}`
|
|
||||||
const effect = shim.createEffectDocument(icon, name, this.durationRounds, [])
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EntangleEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Entangle'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat([
|
|
||||||
{ type: 'radio', label: 'Not Damaging', options: ['dmg', true] },
|
|
||||||
{ type: 'radio', label: 'Damaging', options: ['dmg', false] },
|
|
||||||
{ type: 'radio', label: 'Deadly', options: ['dmg', false] },
|
|
||||||
{ type: 'checkbox', label: 'Tough', options: false }
|
|
||||||
])
|
|
||||||
this.menuData.buttons = [
|
|
||||||
{ label: 'Entangled', value: 'apply' },
|
|
||||||
{ label: 'Bound (raise)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const damage = (this.inputs[this.inputIndex + 1]
|
|
||||||
? '2d4'
|
|
||||||
: (this.inputs[this.inputIndex + 2] ? '2d6' : null))
|
|
||||||
const tough = !!this.inputs[this.inputIndex + 3]
|
|
||||||
const effectSearch = (this.buttons === 'raise' ? 'SWADE.Bound' : 'SWADE.Entangled')
|
|
||||||
const effectName = (this.buttons === 'raise' ? 'Bound' : 'Entangled')
|
|
||||||
const effect = shim.getStatus(effectSearch, effectName)
|
|
||||||
const extraIcon = 'icons/magic/nature/root-vine-barrier-wall-brown.webp'
|
|
||||||
const extraEffect = shim.createEffectDocument(extraIcon,
|
|
||||||
'Entangle Modifier', this.durationRounds, [])
|
|
||||||
if (damage) {
|
|
||||||
extraEffect.name = `${extraEffect.name} - ${damage} dmg`
|
|
||||||
}
|
|
||||||
if (tough) {
|
|
||||||
extraEffect.name = `Tough ${extraEffect.name}`
|
|
||||||
}
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
if (damage || tough) {
|
|
||||||
this.effectDocs.push(extraEffect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FearEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Fear'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.raise = (this.buttons === 'raise')
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
await super.applyResult()
|
|
||||||
await shim.wait(1000)
|
|
||||||
const options = {
|
|
||||||
title: 'Fear check!',
|
|
||||||
flavor: 'Failure: roll on the Fear Table if wildcard, Panicked if extra',
|
|
||||||
mods: []
|
|
||||||
}
|
|
||||||
if (this.raise) {
|
|
||||||
options.fear = '-2'
|
|
||||||
}
|
|
||||||
await requestFearRollFromTokens(this.targets, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HavocEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Havoc'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.raise = (this.buttons === 'raise')
|
|
||||||
this.effectDocs.unshift(shim.getStatus('SWADE.Distr', 'Distracted'))
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
await super.applyResult()
|
|
||||||
await shim.wait(1000)
|
|
||||||
|
|
||||||
const resistMods = function (token) {
|
|
||||||
const mods = []
|
|
||||||
if (token.actor.effects.find(e => e.name === 'Flying')) {
|
|
||||||
mods.push({ label: 'Flying', value: -2 })
|
|
||||||
}
|
|
||||||
return mods
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
title: 'Resisting Havoc!',
|
|
||||||
flavour: 'Havoc!',
|
|
||||||
mods: [],
|
|
||||||
modCallback: resistMods
|
|
||||||
}
|
|
||||||
if (this.raise) {
|
|
||||||
options.mods.push({ label: 'vs. Raise', value: -2 })
|
|
||||||
}
|
|
||||||
|
|
||||||
await requestRollFromTokens(this.targets, 'attribute', 'strength', options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IntangibilityEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Intangility'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (!this.inputs) {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
if (this.inputs[this.inputs.length - 1]) { // Duration
|
|
||||||
return 50
|
|
||||||
}
|
|
||||||
return 5 // no duration
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push({ type: 'checkbox', label: 'Duration', options: false })
|
|
||||||
this.menuData.buttons = [
|
|
||||||
{ label: 'Apply', value: 'apply' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const icon = 'icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp'
|
|
||||||
const effect = shim.createEffectDocument(icon, this.name, this.durationRounds, [])
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class InvisibilityEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Invisiblity'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (!this.inputs) {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
if (this.inputs[this.inputs.length - 1]) { // Duration
|
|
||||||
return 50
|
|
||||||
}
|
|
||||||
return 5 // no duration
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push({ type: 'checkbox', label: 'Duration', options: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const effect = shim.getStatus('EFFECT.StatusInvisible', 'Invisible')
|
|
||||||
effect.duration = { rounds: this.durationRounds }
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProtectionEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Protection'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.buttons = [
|
|
||||||
{ label: 'Apply (+2 armor)', value: 'apply' },
|
|
||||||
{ label: 'Apply with raise (+2 toughness)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const effect = shim.getStatus('SWADE.Protection', 'Protection')
|
|
||||||
effect.duration = { rounds: this.durationRounds }
|
|
||||||
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
|
|
||||||
effect.changes = [
|
|
||||||
{ key: 'system.stats.toughness.armor', mode, value: 2, priority: 0 }
|
|
||||||
]
|
|
||||||
if (this.buttons === 'raise') {
|
|
||||||
effect.changes[0].key = 'system.stats.toughness.value'
|
|
||||||
}
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShapeChangeEffect extends TargetedPowerEffect {
|
|
||||||
get actorFolderBase () {
|
|
||||||
return 'Morphables'
|
|
||||||
}
|
|
||||||
|
|
||||||
get tempActorFolder () {
|
|
||||||
return `${this.actorFolderBase}/Changed`
|
|
||||||
}
|
|
||||||
|
|
||||||
get actorFolder () {
|
|
||||||
return `${this.actorFolderBase}/${this.name}`
|
|
||||||
}
|
|
||||||
|
|
||||||
get name () {
|
|
||||||
return 'Shape Change'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (this.increasedDuration ?? false) {
|
|
||||||
return 50
|
|
||||||
}
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepFolders () {
|
|
||||||
const folders = []
|
|
||||||
const folderNames = [
|
|
||||||
this.actorFolder,
|
|
||||||
`${this.actorFolder} - Default`,
|
|
||||||
`${this.actorFolder}/Default`,
|
|
||||||
`${this.actorFolder} - ${this.token.name}`,
|
|
||||||
`${this.actorFolder} - ${this.token.actor.name}`,
|
|
||||||
`${this.actorFolder}/${this.token.name}`,
|
|
||||||
`${this.actorFolder}/${this.token.actor.name}`
|
|
||||||
]
|
|
||||||
for (const folderName of folderNames) {
|
|
||||||
const folder = shim.getActorFolderByPath(folderName)
|
|
||||||
if (folder) {
|
|
||||||
log(`Found actor folder ${folderName}`)
|
|
||||||
folders.push(folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (folders.length > 1) {
|
|
||||||
folders.shift()
|
|
||||||
}
|
|
||||||
return folders
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepActors () {
|
|
||||||
const folders = await this.prepFolders()
|
|
||||||
const actors = {}
|
|
||||||
for (const folder of folders) {
|
|
||||||
const folderActors = shim.getActorsInFolder(folder)
|
|
||||||
for (const key in folderActors) {
|
|
||||||
actors[key] = folderActors[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return actors
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
const actors = await this.prepActors()
|
|
||||||
this.cancel = false
|
|
||||||
if (Object.keys(actors).length < 1) {
|
|
||||||
shim.notifications.error('No summonables found')
|
|
||||||
this.cancel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function actorData (key) {
|
|
||||||
return {
|
|
||||||
value: actors[key].id,
|
|
||||||
html: key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.summonableActors = actors
|
|
||||||
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat([
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
label: 'Turn into creature',
|
|
||||||
options: Object.keys(actors).filter(
|
|
||||||
k => !k.includes('_template')).sort().map(actorData)
|
|
||||||
}, {
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Duration (+1, rounds to minutes)',
|
|
||||||
options: false
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.raise = (this.buttons === 'raise')
|
|
||||||
this.actorId = (this.inputs[this.inputIndex])
|
|
||||||
this.increasedDuration = (!!this.inputs[this.inputIndex + 1])
|
|
||||||
this.actor = shim.actors.get(this.actorId)
|
|
||||||
this.icon = this.targets[0].document.texture.src
|
|
||||||
const targetActor = this.targets[0].actor
|
|
||||||
this.protoDoc = await this.actor.getTokenDocument()
|
|
||||||
this.spawnOptions = {
|
|
||||||
controllingActor: this.targets[0].actor,
|
|
||||||
duplicates: 1,
|
|
||||||
updateOpts: {
|
|
||||||
embedded: {
|
|
||||||
Item: {
|
|
||||||
renderSheet: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
crosshairs: {
|
|
||||||
rememberControlled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const effectChanges = []
|
|
||||||
if (this.raise) {
|
|
||||||
for (const stat of ['vigor', 'strength']) {
|
|
||||||
effectChanges.push({
|
|
||||||
key: `system.attributes.${stat}.die.sides`,
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
|
|
||||||
value: '+2',
|
|
||||||
priority: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.effectDocs.push(
|
|
||||||
shim.createEffectDocument(
|
|
||||||
this.icon,
|
|
||||||
`Shape Change into ${this.protoDoc.name}`,
|
|
||||||
this.durationRounds, effectChanges)
|
|
||||||
)
|
|
||||||
|
|
||||||
this.spawnMutation = {
|
|
||||||
actor: {
|
|
||||||
name: `${this.targets[0].actor.name} (${this.actor.name} form)`,
|
|
||||||
system: {
|
|
||||||
attributes: {
|
|
||||||
smarts: { die: targetActor.system.attributes.smarts.die },
|
|
||||||
spirit: { die: targetActor.system.attributes.spirit.die }
|
|
||||||
},
|
|
||||||
wildcard: targetActor.system.wildcard
|
|
||||||
}
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
flags: {
|
|
||||||
'swade-mb-helpers.shapeChange.srcTokenId': this.targets[0].id
|
|
||||||
},
|
|
||||||
actorLink: false,
|
|
||||||
name: `${this.targets[0].name} (${this.protoDoc.name} form) `,
|
|
||||||
elevation: this.targets[0].document.elevation,
|
|
||||||
disposition: this.targets[0].document.disposition,
|
|
||||||
sight: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
embedded: { ActiveEffect: {}, Item: {} }
|
|
||||||
}
|
|
||||||
for (const doc of this.effectDocs) {
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[doc.name] = doc
|
|
||||||
}
|
|
||||||
for (const doc of this.targets[0].actor.effects) {
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[doc.name] = this.targets[0].actor.getEmbeddedDocument('ActiveEffect', doc.id)
|
|
||||||
}
|
|
||||||
for (const item of targetActor.items) {
|
|
||||||
if (item.type === 'skill' && ['smarts', 'spirit'].includes(item.system.attribute)) {
|
|
||||||
const doc = await this.targets[0].actor.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = doc
|
|
||||||
}
|
|
||||||
if (['power', 'edge', 'hindrance', 'action'].includes(item.type)) {
|
|
||||||
const doc = await this.targets[0].actor.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
log('protoDoc', this.protoDoc)
|
|
||||||
log('spawnOptions', this.spawnOptions)
|
|
||||||
log('spawnMutation', this.spawnMutation)
|
|
||||||
const newTokenId = (await shim.warpgateSpawnAt(
|
|
||||||
this.targets[0].center,
|
|
||||||
this.protoDoc,
|
|
||||||
this.spawnMutation,
|
|
||||||
{},
|
|
||||||
this.spawnOptions
|
|
||||||
))[0]
|
|
||||||
await this.targets[0].document.setFlag('swade-mb-helpers', 'shapeChange', {
|
|
||||||
toId: newTokenId,
|
|
||||||
saved: {
|
|
||||||
alpha: this.targets[0].document.alpha,
|
|
||||||
hidden: this.targets[0].document.hidden,
|
|
||||||
x: this.targets[0].document.x,
|
|
||||||
y: this.targets[0].document.y,
|
|
||||||
elevation: this.targets[0].document.elevation
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await this.targets[0].document.update({
|
|
||||||
hidden: true,
|
|
||||||
alpha: 0.05
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SmiteEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Smite'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs.push({
|
|
||||||
type: 'checkbox', label: 'Greater', options: false
|
|
||||||
})
|
|
||||||
const tokenWeapons = {}
|
|
||||||
let index = this.menuData.inputs.length - 1
|
|
||||||
for (const token of this.targets) {
|
|
||||||
index += 2
|
|
||||||
tokenWeapons[token.id] = index
|
|
||||||
this.menuData.inputs.push({ type: 'info', label: `<h2>${token.name}</h2>` })
|
|
||||||
const weapons = token.actor.items.filter(i => i.type === 'weapon').map(
|
|
||||||
i => { return { value: i.name, html: i.name } })
|
|
||||||
weapons.unshift({ value: '', html: '<i>None</i>' })
|
|
||||||
this.menuData.inputs.push({ type: 'select', label: token.name, options: weapons })
|
|
||||||
}
|
|
||||||
this.tokenWeapons = tokenWeapons
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.baseEffect = shim.getStatus('SWADE.Smite', 'Smite')
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
const mode = CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const greater = !!this.inputs[this.inputIndex]
|
|
||||||
const changeValue = (greater ? (raise ? '+6' : '+4') : (raise ? '+4' : '+2'))
|
|
||||||
const changeKey = 'system.stats.globalMods.damage'
|
|
||||||
for (const token of this.targets) {
|
|
||||||
const weaponName = this.inputs[this.tokenWeapons[token.id]]
|
|
||||||
const effectName = `Smite (${weaponName})`
|
|
||||||
const changes = [
|
|
||||||
{ key: changeKey, mode, value: changeValue, priority: 0 }
|
|
||||||
]
|
|
||||||
this.baseEffect.changes = changes
|
|
||||||
this.baseEffect.name = effectName
|
|
||||||
await shim.applyActiveEffects(token, [this.baseEffect].concat(this.effectDocs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SlothSpeedEffect extends TargetedPowerEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Sloth/Speed'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
if (this.inputs?.[this.inputIndex + 1] === true) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat(
|
|
||||||
{ type: 'info', label: 'Sloth or Speed?' },
|
|
||||||
{ type: 'radio', label: 'Sloth', options: ['isSloth', true] },
|
|
||||||
{ type: 'radio', label: 'Speed', options: ['isSloth', false] },
|
|
||||||
{ type: 'checkbox', label: 'Dash (+2, speed only)', options: false },
|
|
||||||
{ type: 'checkbox', label: 'Quickness (+2, speed only)', options: false },
|
|
||||||
{ type: 'checkbox', label: 'Strong (+1, sloth only)', options: false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
const raise = (this.buttons === 'raise')
|
|
||||||
const isSloth = this.inputs[this.inputIndex + 1] === true
|
|
||||||
const icon = (isSloth
|
|
||||||
? 'icons/magic/control/debuff-chains-shackles-movement-blue.webp'
|
|
||||||
: 'icons/skills/movement/feet-winged-sandals-tan.webp'
|
|
||||||
)
|
|
||||||
const dash = !isSloth && !!this.inputs[this.inputIndex + 3]
|
|
||||||
const quickness = !isSloth && !!this.inputs[this.inputIndex + 4]
|
|
||||||
const strong = isSloth && !!this.inputs[this.inputIndex + 4]
|
|
||||||
const nameMods = []
|
|
||||||
if (raise) { nameMods.push('Major') }
|
|
||||||
if (dash) { nameMods.push('Dash') }
|
|
||||||
if (quickness) { nameMods.push('Quickness') }
|
|
||||||
if (strong) { nameMods.push('Strong') }
|
|
||||||
const nameModifier = (
|
|
||||||
`${nameMods.length > 0 ? ' (' : ''}` +
|
|
||||||
`${nameMods.join(', ')}${nameMods.length > 0 ? ')' : ''}`)
|
|
||||||
const name = `${isSloth ? 'Sloth' : 'Speed'}${nameModifier}`
|
|
||||||
const effect = shim.createEffectDocument(
|
|
||||||
icon, name, this.durationRounds, [
|
|
||||||
{
|
|
||||||
key: 'system.stats.speed.value',
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.MULTIPLY,
|
|
||||||
value: (isSloth ? 0.5 : 2),
|
|
||||||
priority: 0
|
|
||||||
}])
|
|
||||||
this.effectDocs.push(effect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonEffect extends PowerEffect {
|
|
||||||
ICON = 'icons/magic/symbols/runes-triangle-blue.webp'
|
|
||||||
|
|
||||||
get actorFolderBase () {
|
|
||||||
return 'Summonables'
|
|
||||||
}
|
|
||||||
|
|
||||||
get actorFolder () {
|
|
||||||
return `${this.actorFolderBase}/${this.name}`
|
|
||||||
}
|
|
||||||
|
|
||||||
get name () {
|
|
||||||
return 'Summon Creature'
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseDurationRounds () {
|
|
||||||
return 5
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepFolders () {
|
|
||||||
const folders = []
|
|
||||||
const folderNames = [
|
|
||||||
this.actorFolder,
|
|
||||||
`${this.actorFolder} - Default`,
|
|
||||||
`${this.actorFolder}/Default`,
|
|
||||||
`${this.actorFolder} - ${this.token.name}`,
|
|
||||||
`${this.actorFolder} - ${this.token.actor.name}`,
|
|
||||||
`${this.actorFolder}/${this.token.name}`,
|
|
||||||
`${this.actorFolder}/${this.token.actor.name}`
|
|
||||||
]
|
|
||||||
for (const folderName of folderNames) {
|
|
||||||
const folder = shim.getActorFolderByPath(folderName)
|
|
||||||
if (folder) {
|
|
||||||
log(`Found actor folder ${folderName}`)
|
|
||||||
folders.push(folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (folders.length > 1) {
|
|
||||||
folders.shift()
|
|
||||||
}
|
|
||||||
return folders
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepActors () {
|
|
||||||
const folders = await this.prepFolders()
|
|
||||||
const actors = {}
|
|
||||||
for (const folder of folders) {
|
|
||||||
const folderActors = shim.getActorsInFolder(folder)
|
|
||||||
for (const key in folderActors) {
|
|
||||||
actors[key] = folderActors[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return actors
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
this.menuData.inputs[1].label = `${this.token.name} is summoning...`
|
|
||||||
this.menuData.buttons = this.menuData.buttons.filter(b => b.value !== 'raise')
|
|
||||||
const actors = await this.prepActors()
|
|
||||||
if (Object.keys(actors).length < 1) {
|
|
||||||
shim.notifications.error('No summonables found')
|
|
||||||
throw new Error('No summonables found')
|
|
||||||
}
|
|
||||||
|
|
||||||
function actorData (key) {
|
|
||||||
return {
|
|
||||||
value: actors[key].id,
|
|
||||||
html: key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.summonableActors = actors
|
|
||||||
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat([
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
label: 'Creature to summon',
|
|
||||||
options: Object.keys(actors).filter(
|
|
||||||
k => !k.includes('_template')).sort().map(actorData)
|
|
||||||
}, {
|
|
||||||
type: 'number',
|
|
||||||
label: 'Number to spawn (+half base cost per)',
|
|
||||||
options: 1
|
|
||||||
}, {
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Add Increased Trait(s)? (+1 per trait)',
|
|
||||||
options: false
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.raise = (this.buttons === 'raise')
|
|
||||||
this.actorId = (this.inputs[this.inputIndex])
|
|
||||||
this.number = (this.inputs[this.inputIndex + 1])
|
|
||||||
this.actor = shim.actors.get(this.actorId)
|
|
||||||
this.icon = this.actor.prototypeToken.texture.src
|
|
||||||
this.protoDoc = await this.actor.getTokenDocument()
|
|
||||||
this.increasedTrait = !!(this.inputs[this.inputIndex + 2])
|
|
||||||
this.inputIndex += 3
|
|
||||||
this.spawnOptions = {
|
|
||||||
controllingActor: this.token.actor,
|
|
||||||
duplicates: this.number,
|
|
||||||
updateOpts: {
|
|
||||||
embedded: {
|
|
||||||
Item: {
|
|
||||||
renderSheet: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
crosshairs: {
|
|
||||||
icon: this.icon,
|
|
||||||
label: `Summon ${this.actor.name}`,
|
|
||||||
drawOutline: true,
|
|
||||||
rememberControlled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.spawnMutation = {
|
|
||||||
actor: {
|
|
||||||
name: `${this.token.name}'s ${this.actor.name}`
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
actorLink: false,
|
|
||||||
name: `${this.token.name}'s ${this.protoDoc.name}`,
|
|
||||||
disposition: this.token.document.disposition,
|
|
||||||
sight: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
embedded: { ActiveEffect: {}, Item: {} }
|
|
||||||
}
|
|
||||||
if (this.raise && ('raise_template' in this.summonableActors)) {
|
|
||||||
const raiseTemplate = this.summonableActors.raise_template
|
|
||||||
for (const item of raiseTemplate.items) {
|
|
||||||
const raiseItemDoc = await raiseTemplate.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = raiseItemDoc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const effectDocument of this.effectDocs) {
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepAdditional () {
|
|
||||||
if (!this.increasedTrait) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const traitMenuOptions = {
|
|
||||||
title: `${this.name} Summon Trait Increase`,
|
|
||||||
defaultButton: 'Cancel',
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
const skillSet = new Set()
|
|
||||||
for (const skill of this.actor.items.filter(i => i.type === 'skill')) {
|
|
||||||
skillSet.add(skill.name)
|
|
||||||
}
|
|
||||||
for (const item of Object.values(this.spawnMutation.embedded.Item).filter(i => i.type === 'skill')) {
|
|
||||||
skillSet.add(item.name)
|
|
||||||
}
|
|
||||||
const skillList = Array.from(skillSet)
|
|
||||||
const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
|
|
||||||
skillList.sort()
|
|
||||||
const traitMenuData = {
|
|
||||||
inputs: [
|
|
||||||
{ type: 'header', label: 'Increase Attributes (+1 each)' }
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{ label: 'Apply', value: 'apply' },
|
|
||||||
{ label: 'Increase no traits', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
traitMenuData.inputs = traitMenuData.inputs.concat(
|
|
||||||
attrList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
|
|
||||||
traitMenuData.inputs.push({ type: 'header', label: 'Increase Skills (+1 each)' })
|
|
||||||
traitMenuData.inputs = traitMenuData.inputs.concat(
|
|
||||||
skillList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
|
|
||||||
const { buttons, inputs } = await shim.warpgateMenu(traitMenuData, traitMenuOptions)
|
|
||||||
if (!buttons || buttons === 'cancel') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const modKeys = []
|
|
||||||
for (let i = 0; i < attrList.length; i++) {
|
|
||||||
if (inputs[i + 1]) {
|
|
||||||
modKeys.push(`system.attributes.${attrList[i].toLowerCase()}.die.sides`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < skillList.length; i++) {
|
|
||||||
if (inputs[i + 7]) {
|
|
||||||
modKeys.push(`@Skill{${skillList[i]}}[system.die.sides]`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const effectDoc = shim.createEffectDocument(
|
|
||||||
this.ICON, 'Increased Trait', this.durationRounds)
|
|
||||||
effectDoc.changes = modKeys.map(key => {
|
|
||||||
return {
|
|
||||||
key, mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD, value: '+2', priority: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
|
|
||||||
}
|
|
||||||
|
|
||||||
async applyResult () {
|
|
||||||
await this.prepAdditional()
|
|
||||||
await shim.warpgateSpawn(this.protoDoc, this.spawnMutation, {}, this.spawnOptions)
|
|
||||||
log('protoDoc', this.protoDoc)
|
|
||||||
log('spawnOptions', this.spawnOptions)
|
|
||||||
log('spawnMutation', this.spawnMutation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonAllyEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Summon Ally'
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
await super.prepMenu()
|
|
||||||
this.menuData.buttons = [
|
|
||||||
this.menuData.buttons[0],
|
|
||||||
{ label: 'Apply with Raise', value: 'raise' },
|
|
||||||
this.menuData.buttons[1]
|
|
||||||
]
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat([
|
|
||||||
{
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Bite/Claw (+1)',
|
|
||||||
options: false
|
|
||||||
}, {
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Up to 3 Combat Edges (+1 per)',
|
|
||||||
options: false
|
|
||||||
}, {
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Flight (+3)',
|
|
||||||
options: false
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
await super.prepResult()
|
|
||||||
this.biteClaw = !!(this.inputs[this.inputIndex])
|
|
||||||
this.combatEdge = !!(this.inputs[this.inputIndex + 1])
|
|
||||||
this.flight = !!(this.inputs[this.inputIndex + 2])
|
|
||||||
await this.prepMirrorSelf()
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepAdditional () {
|
|
||||||
await super.prepAdditional()
|
|
||||||
await this.prepBiteClaw()
|
|
||||||
await this.prepFlight()
|
|
||||||
await this.prepCombatEdge()
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepCombatEdge () {
|
|
||||||
if (!this.combatEdge || !('combat-edge_template' in this.summonableActors)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const template = this.summonableActors['combat-edge_template']
|
|
||||||
const edges = template.items.filter(i => i.type === 'edge').map(i => i.name)
|
|
||||||
edges.sort()
|
|
||||||
edges.unshift('None')
|
|
||||||
const edgeMenuData = {
|
|
||||||
inputs: [
|
|
||||||
{ type: 'header', label: 'Choose Edges (+1 per choice)' },
|
|
||||||
{ type: 'select', label: 'Edge 1', options: edges },
|
|
||||||
{ type: 'select', label: 'Edge 2', options: edges },
|
|
||||||
{ type: 'select', label: 'Edge 3', options: edges }
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{ label: 'Apply', value: 'apply' },
|
|
||||||
{ label: 'Add no edges', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const edgeMenuOptions = {
|
|
||||||
title: `${this.name} Combat Edge Selection`,
|
|
||||||
defaultButton: 'Cancel',
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
const { buttons, inputs } = await shim.warpgateMenu(edgeMenuData, edgeMenuOptions)
|
|
||||||
if (!buttons || buttons === 'cancel') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (let i = 1; i <= 3; i++) {
|
|
||||||
if (inputs[i] === 'None') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const edge = template.items.getName(inputs[i])
|
|
||||||
if (edge) {
|
|
||||||
const doc = await template.getEmbeddedDocument('Item', edge.id)
|
|
||||||
this.spawnMutation.embedded.Item[edge.name] = doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepBiteClaw () {
|
|
||||||
if (!this.biteClaw || !('bite-claw_template' in this.summonableActors)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const template = this.summonableActors['bite-claw_template']
|
|
||||||
for (const item of template.items) {
|
|
||||||
const doc = await template.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepFlight () {
|
|
||||||
if (!this.flight || !('flight_template' in this.summonableActors)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const template = this.summonableActors.flight_template
|
|
||||||
for (const item of template.items) {
|
|
||||||
const doc = await template.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = doc
|
|
||||||
}
|
|
||||||
for (const effect of template.effects.values()) {
|
|
||||||
const doc = shim.ActiveEffect.fromSource(effect)
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effect.name] = doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMirrorSelf () {
|
|
||||||
if (this.actor.name !== 'Mirror Self') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const mirrorActor = this.token.actor
|
|
||||||
this.icon = mirrorActor.prototypeToken.texture.src
|
|
||||||
this.spawnMutation.actor.system = mirrorActor.system.clone({
|
|
||||||
wildcard: false,
|
|
||||||
'fatigue.value': 0,
|
|
||||||
'wounds.value': 0,
|
|
||||||
'wounds.max': 0,
|
|
||||||
'bennies.max': 0,
|
|
||||||
'bennies.value': 0
|
|
||||||
})
|
|
||||||
this.spawnMutation.actor.name = `Mirror ${mirrorActor.name}`
|
|
||||||
this.spawnMutation.actor.img = mirrorActor.img
|
|
||||||
this.spawnMutation.token.name = `Mirror ${this.token.name}`
|
|
||||||
this.spawnMutation.token.texture = {
|
|
||||||
src: this.token.document.texture.src,
|
|
||||||
scaleX: this.token.document.texture.scaleX * -1,
|
|
||||||
scaleY: this.token.document.texture.scaleY
|
|
||||||
}
|
|
||||||
this.spawnOptions.crosshairs.icon = this.icon
|
|
||||||
const effectChanges = []
|
|
||||||
for (const mirrorItem of mirrorActor.items) {
|
|
||||||
if (mirrorItem.type === 'power' &&
|
|
||||||
(mirrorItem.system?.swid === 'summon-ally' || mirrorItem.name === 'Summon Ally')) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (['weapon', 'armor', 'consumable', 'gear'].includes(mirrorItem.type)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
this.spawnMutation.embedded.Item[mirrorItem.name] = await mirrorActor.getEmbeddedDocument(
|
|
||||||
'Item', mirrorItem.id)
|
|
||||||
if (mirrorItem.type === 'skill') {
|
|
||||||
effectChanges.push({
|
|
||||||
key: `@Skill{${mirrorItem.name}}[system.die.sides]`,
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
|
|
||||||
value: '-2',
|
|
||||||
priority: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.spawnMutation.embedded.ActiveEffect['Mirror Self'] =
|
|
||||||
shim.createEffectDocument(this.ICON, 'Mirror Self',
|
|
||||||
this.durationRounds, effectChanges)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonAnimalEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Summon Animal'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonMonsterEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Summon Monster'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonNaturesAllyEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return "Summon Nature's Ally"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonPlanarAllyEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Summon Planar Ally'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SummonUndeadEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Summon Undead'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ZombieEffect extends SummonEffect {
|
|
||||||
get name () {
|
|
||||||
return 'Zombie'
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepMenu () {
|
|
||||||
await super.prepMenu()
|
|
||||||
this.menuData.buttons = [
|
|
||||||
this.menuData.buttons[0],
|
|
||||||
{ label: 'Apply with Raise', value: 'raise' },
|
|
||||||
this.menuData.buttons[1]
|
|
||||||
]
|
|
||||||
this.menuData.inputs.pop()
|
|
||||||
this.menuData.inputs = this.menuData.inputs.concat([
|
|
||||||
{
|
|
||||||
type: 'checkbox',
|
|
||||||
label: 'Armed (Hand Weapon (Str+d6) or Ranged Weapon (2d6)',
|
|
||||||
options: false
|
|
||||||
}, {
|
|
||||||
type: 'checkbox',
|
|
||||||
label: '+2 Armor',
|
|
||||||
options: false
|
|
||||||
}, {
|
|
||||||
type: 'info',
|
|
||||||
label: 'Skeletal creatures +1 per zombie'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepResult () {
|
|
||||||
this.raise = (this.buttons === 'raise')
|
|
||||||
this.actorId = this.inputs[this.inputIndex]
|
|
||||||
this.number = this.inputs[this.inputIndex + 1]
|
|
||||||
this.actor = shim.actors.get(this.actorId)
|
|
||||||
this.icon = this.actor.prototypeToken.texture.src
|
|
||||||
this.protoDoc = await this.actor.getTokenDocument()
|
|
||||||
this.increasedTrait = this.raise
|
|
||||||
this.armed = this.inputs[this.inputIndex + 2]
|
|
||||||
this.armor = this.inputs[this.inputIndex + 3]
|
|
||||||
this.spawnOptions = {
|
|
||||||
controllingActor: this.token.actor,
|
|
||||||
duplicates: this.number,
|
|
||||||
updateOpts: {
|
|
||||||
embedded: {
|
|
||||||
Item: { renderSheet: null }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
crosshairs: {
|
|
||||||
icon: this.icon,
|
|
||||||
label: `Summon ${this.actor.name}`,
|
|
||||||
drawOutline: true,
|
|
||||||
rememberControlled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.spawnMutation = {
|
|
||||||
actor: {
|
|
||||||
name: `${this.token.name}'s ${this.actor.name}`
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
actorLink: false,
|
|
||||||
name: `${this.token.name}'s ${this.protoDoc.name}`,
|
|
||||||
disposition: this.token.document.disposition,
|
|
||||||
sight: { enabled: true }
|
|
||||||
},
|
|
||||||
embedded: { ActiveEffect: {}, Item: {} }
|
|
||||||
}
|
|
||||||
if (this.armed && ('armed_template' in this.summonableActors)) {
|
|
||||||
const armedTemplate = this.summonableActors.armed_template
|
|
||||||
for (const item of armedTemplate.items) {
|
|
||||||
const itemDoc = await armedTemplate.getEmbeddedDocument('Item', item.id)
|
|
||||||
this.spawnMutation.embedded.Item[item.name] = itemDoc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.armor) {
|
|
||||||
const effectDoc = shim.createEffectDocument(
|
|
||||||
'icons/equipment/chest/breastplate-layered-leather-stitched.webp',
|
|
||||||
'Rotting Armor',
|
|
||||||
0)
|
|
||||||
delete effectDoc.duration
|
|
||||||
delete effectDoc.flags.swade.expiration
|
|
||||||
effectDoc.changes = [{
|
|
||||||
key: 'system.stats.toughness.armor',
|
|
||||||
mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD,
|
|
||||||
value: '+2',
|
|
||||||
priority: 0
|
|
||||||
}]
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
|
|
||||||
}
|
|
||||||
for (const effectDocument of this.effectDocs) {
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async prepAdditional () {
|
|
||||||
if (!this.increasedTrait) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const traitMenuOptions = {
|
|
||||||
title: `${this.name} Raise Trait Increase`,
|
|
||||||
defaultButton: 'Cancel',
|
|
||||||
options: {}
|
|
||||||
}
|
|
||||||
const skillSet = new Set()
|
|
||||||
for (const skill of this.actor.items.filter(i => i.type === 'skill')) {
|
|
||||||
skillSet.add(skill.name)
|
|
||||||
}
|
|
||||||
for (const item of Object.values(this.spawnMutation.embedded.Item).filter(i => i.type === 'skill')) {
|
|
||||||
skillSet.add(item.name)
|
|
||||||
}
|
|
||||||
const skillList = Array.from(skillSet)
|
|
||||||
const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
|
|
||||||
skillList.sort()
|
|
||||||
const traitMenuData = {
|
|
||||||
inputs: [
|
|
||||||
{ type: 'header', label: 'Raise! Increase an attribute' }
|
|
||||||
],
|
|
||||||
buttons: [
|
|
||||||
{ label: 'Apply', value: 'apply' },
|
|
||||||
{ label: 'Increase no traits', value: 'cancel' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
traitMenuData.inputs = traitMenuData.inputs.concat(
|
|
||||||
attrList.map((x) => { return { type: 'radio', label: x, options: ['trait', false] } }))
|
|
||||||
traitMenuData.inputs.push({ type: 'header', label: 'Increase Skills (+1 each)' })
|
|
||||||
traitMenuData.inputs = traitMenuData.inputs.concat(
|
|
||||||
skillList.map((x) => { return { type: 'radio', label: x, options: ['trait', false] } }))
|
|
||||||
const { buttons, inputs } = await shim.warpgateMenu(traitMenuData, traitMenuOptions)
|
|
||||||
if (!buttons || buttons === 'cancel') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const modKeys = []
|
|
||||||
for (let i = 0; i < attrList.length; i++) {
|
|
||||||
if (inputs[i + 1]) {
|
|
||||||
modKeys.push(`system.attributes.${attrList[i].toLowerCase()}.die.sides`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < skillList.length; i++) {
|
|
||||||
if (inputs[i + 7]) {
|
|
||||||
modKeys.push(`@Skill{${skillList[i]}}[system.die.sides]`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const effectDoc = shim.createEffectDocument(
|
|
||||||
this.ICON, 'Increased Trait', this.durationRounds)
|
|
||||||
effectDoc.changes = modKeys.map(key => {
|
|
||||||
return {
|
|
||||||
key, mode: CONST.FOUNDRY.ACTIVE_EFFECT_MODES.ADD, value: '+2', priority: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PowerClasses = {
|
|
||||||
'arcane protection': ArcaneProtectionEffect,
|
|
||||||
'arcane-protection': ArcaneProtectionEffect,
|
|
||||||
blast: BlastEffect,
|
|
||||||
blind: BlindEffect,
|
|
||||||
bolt: BoltEffect,
|
|
||||||
'boost/lower trait': BoostLowerTraitEffect,
|
|
||||||
'boostlower-trait': BoostLowerTraitEffect,
|
|
||||||
'boost trait': BoostLowerTraitEffect,
|
|
||||||
'boost-trait': BoostLowerTraitEffect,
|
|
||||||
burrow: BurrowEffect,
|
|
||||||
burst: BurstEffect,
|
|
||||||
'conceal arcana': DetectConcealArcanaEffect,
|
|
||||||
'conceal-arcana': DetectConcealArcanaEffect,
|
|
||||||
confusion: ConfusionEffect,
|
|
||||||
darksight: DarksightEffect,
|
|
||||||
deflection: DeflectionEffect,
|
|
||||||
'detect arcana': DetectConcealArcanaEffect,
|
|
||||||
'detect-arcana': DetectConcealArcanaEffect,
|
|
||||||
'detect/conceal aracana': DetectConcealArcanaEffect,
|
|
||||||
'detectconceal-aracana': DetectConcealArcanaEffect,
|
|
||||||
disguise: DisguiseEffect,
|
|
||||||
entangle: EntangleEffect,
|
|
||||||
fear: FearEffect,
|
|
||||||
havoc: HavocEffect,
|
|
||||||
intangibility: IntangibilityEffect,
|
|
||||||
invisibility: InvisibilityEffect,
|
|
||||||
'lower trait': BoostLowerTraitEffect,
|
|
||||||
'lower-trait': BoostLowerTraitEffect,
|
|
||||||
protection: ProtectionEffect,
|
|
||||||
'shape change': ShapeChangeEffect,
|
|
||||||
'shape-change': ShapeChangeEffect,
|
|
||||||
sloth: SlothSpeedEffect,
|
|
||||||
'sloth/speed': SlothSpeedEffect,
|
|
||||||
slothspeed: SlothSpeedEffect,
|
|
||||||
smite: SmiteEffect,
|
|
||||||
speed: SlothSpeedEffect,
|
|
||||||
'summon ally': SummonAllyEffect,
|
|
||||||
'summon-ally': SummonAllyEffect,
|
|
||||||
'summon animal': SummonAnimalEffect,
|
|
||||||
'summon-animal': SummonAnimalEffect,
|
|
||||||
'summon monster': SummonMonsterEffect,
|
|
||||||
'summon-monster': SummonMonsterEffect,
|
|
||||||
"summon nature's ally": SummonNaturesAllyEffect,
|
|
||||||
'summon-natures-ally': SummonNaturesAllyEffect,
|
|
||||||
'summon planar ally': SummonPlanarAllyEffect,
|
|
||||||
'summon-planar-ally': SummonPlanarAllyEffect,
|
|
||||||
'summon undead': SummonUndeadEffect,
|
|
||||||
'summon-undead': SummonUndeadEffect,
|
|
||||||
zombie: ZombieEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function powerEffects (options = {}) {
|
|
||||||
const token = 'token' in options ? options.token : []
|
|
||||||
if (token === undefined || token === null) {
|
|
||||||
shim.notifications.error('Please select one token')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const targets = 'targets' in options ? Array.from(options.targets) : []
|
|
||||||
const item = 'item' in options ? options.item : null
|
|
||||||
const name = options?.name || item?.system?.swid || item?.name || null
|
|
||||||
|
|
||||||
const lcName = name.toLowerCase()
|
|
||||||
for (const name in PowerClasses) {
|
|
||||||
if (lcName.includes(name)) {
|
|
||||||
const runner = new PowerClasses[name](token, targets)
|
|
||||||
runner.powerEffect()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shim.notifications.error(`No power effect found for ${name}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function shapeChangeOnDismiss (data) {
|
|
||||||
if (shim.user.id !== data.userId) { return }
|
|
||||||
const dismissedToken = data.actorData.prototypeToken
|
|
||||||
const flags = dismissedToken.flags['swade-mb-helpers']?.shapeChange
|
|
||||||
const srcTokenId = flags?.srcTokenId
|
|
||||||
if (!srcTokenId) { return }
|
|
||||||
const scene = shim.scenes.get(data.sceneId)
|
|
||||||
const token = scene.tokens.get(srcTokenId)
|
|
||||||
if (!token) { return }
|
|
||||||
const saved = token.flags['swade-mb-helpers']?.shapeChange?.saved
|
|
||||||
if (saved) {
|
|
||||||
const update = {
|
|
||||||
alpha: saved.alpha,
|
|
||||||
hidden: saved.hidden,
|
|
||||||
x: dismissedToken.x,
|
|
||||||
y: dismissedToken.y,
|
|
||||||
elevation: dismissedToken.elevation
|
|
||||||
}
|
|
||||||
await token.update(update)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
import { log, shim } from './shim.js'
|
|
||||||
|
|
||||||
export async function preTraitRollModifiers (actor, trait, roll, modifiers, options) {
|
|
||||||
const targets = Array.from(shim.targets)
|
|
||||||
const token = shim.canvas.tokens.controlled.length > 0 ? shim.canvas.tokens.controlled[0] : null
|
|
||||||
// log('ACTOR', actor)
|
|
||||||
// log('TOKEN', token)
|
|
||||||
// log('TRAIT', trait)
|
|
||||||
// log('ROLL', roll)
|
|
||||||
// log('MODIFIERS', modifiers)
|
|
||||||
// log('OPTIONS', options)
|
|
||||||
// log('TARGET', targets)
|
|
||||||
if (targets.some(target => target.actor.system.status.isVulnerable)) {
|
|
||||||
modifiers.push({ label: 'Target is Vulnerable', value: '+2', ignore: false })
|
|
||||||
}
|
|
||||||
if (targets.some(
|
|
||||||
target => target.actor.effects.filter(
|
|
||||||
e => !e.disabled && e.name.toLowerCase().includes('deflection')).length > 0)
|
|
||||||
) {
|
|
||||||
modifiers.push({ label: 'Target has Deflection', value: '-2', ignore: false })
|
|
||||||
}
|
|
||||||
if (targets.some(
|
|
||||||
target => target.actor.effects.filter(
|
|
||||||
e => !e.disabled && e.name.toLowerCase().includes('glow')).length > 0)
|
|
||||||
) {
|
|
||||||
modifiers.push({
|
|
||||||
label: 'Glowing target (negate 1 point of illumination penalty)',
|
|
||||||
value: '+1',
|
|
||||||
ignore: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (targets.some(
|
|
||||||
target => target.actor.effects.filter(
|
|
||||||
e => !e.disabled && e.name.toLowerCase().includes('shroud')).length > 0)
|
|
||||||
) {
|
|
||||||
modifiers.push({
|
|
||||||
label: 'Shrouded target',
|
|
||||||
value: '-1',
|
|
||||||
ignore: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (targets.length === 1 && token) {
|
|
||||||
const target = targets[0]
|
|
||||||
_addArcaneModifiers(target, modifiers)
|
|
||||||
_addRangeModifiers(token, target, options, modifiers)
|
|
||||||
const scaleMod = calcScaleMod(token, target)
|
|
||||||
if (scaleMod !== 0) {
|
|
||||||
modifiers.push({ label: 'Scale', value: scaleMod, ignore: false })
|
|
||||||
}
|
|
||||||
if (target.actor.items.find(e => e.type === 'edge' && e.system.swid === 'dodge')) {
|
|
||||||
modifiers.push({ label: 'Dodge', value: -2, ignore: true })
|
|
||||||
}
|
|
||||||
if (trait?.type === 'skill' && trait?.system?.swid === 'fighting') {
|
|
||||||
const gangUpBonus = calcGangup(token, target)
|
|
||||||
if (gangUpBonus > 0) {
|
|
||||||
modifiers.push({ label: 'Gang Up', value: gangUpBonus, ignore: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function preDamageRollModifiers (actor, item, roll, modifiers, options) {
|
|
||||||
const targets = Array.from(shim.targets)
|
|
||||||
const token = shim.canvas.tokens.controlled.length > 0 ? shim.canvas.tokens.controlled[0] : null
|
|
||||||
// log('ACTOR', actor)
|
|
||||||
// log('TOKEN', token)
|
|
||||||
// log('ITEM', item)
|
|
||||||
// log('ROLL', roll)
|
|
||||||
// log('MODIFIERS', modifiers)
|
|
||||||
// log('OPTIONS', options)
|
|
||||||
// log('TARGET', targets)
|
|
||||||
if (targets.length === 1 && token) {
|
|
||||||
const target = targets[0]
|
|
||||||
_addArcaneModifiers(target, modifiers)
|
|
||||||
const weaknesses = target.actor.items.filter(
|
|
||||||
i => i.type === 'ability' && i.system.swid.toLowerCase().includes('weakness'))
|
|
||||||
if (weaknesses.length > 0) {
|
|
||||||
modifiers.push(...weaknesses.map(i => { return { label: i.name, value: '+4', ignore: true } }))
|
|
||||||
}
|
|
||||||
const resistances = target.actor.items.filter(
|
|
||||||
i => i.type === 'ability' && i.system.swid.toLowerCase().includes('resistance'))
|
|
||||||
if (resistances.length > 0) {
|
|
||||||
modifiers.push(...resistances.map(i => { return { label: i.name, value: '-4', ignore: true } }))
|
|
||||||
}
|
|
||||||
if (_findItem(token.actor, 'ability', 'pack-tactics')) {
|
|
||||||
const gangupBonus = calcGangup(token, target)
|
|
||||||
if (gangupBonus > 0) {
|
|
||||||
modifiers.push({ label: 'Gang Up (Pack Tactics)', value: gangupBonus, ignore: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _addRangeModifiers (token, target, options, modifiers) {
|
|
||||||
if (options?.item?.type !== 'weapon' || !options?.item?.system?.range.includes('/')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const ranges = options.item.system.range.split('/').map(x => parseInt(x))
|
|
||||||
const distance = getDistance(token, target)
|
|
||||||
const rollmods = shim.CONFIG.SWADE.prototypeRollGroups.find(g => g.name === 'Range').modifiers
|
|
||||||
log('ITEM RANGES:', ranges)
|
|
||||||
if (distance <= ranges[0]) {
|
|
||||||
// nothing here
|
|
||||||
} else if (ranges.length >= 2 && distance <= ranges[1]) {
|
|
||||||
modifiers.push(rollmods[0])
|
|
||||||
} else if (ranges.length >= 3 && distance <= ranges[2]) {
|
|
||||||
modifiers.push(rollmods[1])
|
|
||||||
} else {
|
|
||||||
modifiers.push(rollmods[2]) // extreme range
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _addArcaneModifiers (target, modifiers) {
|
|
||||||
if (_findItem(target.actor, 'edge', 'improved-arcane-resistance')) {
|
|
||||||
modifiers.push({ label: 'Arcane Resistance', value: '-4', ignore: true })
|
|
||||||
} else if (_findItem(target.actor, 'edge', 'arcane-resistance')) {
|
|
||||||
modifiers.push({ label: 'Arcane Resistance', value: '-2', ignore: true })
|
|
||||||
}
|
|
||||||
const effect = target.actor.effects.find(
|
|
||||||
e => !e.disabled && e.name.toLowerCase().includes('arcane protection'))
|
|
||||||
if (effect) {
|
|
||||||
const effectName = effect.name.toLowerCase()
|
|
||||||
const effectMod = (
|
|
||||||
-2 +
|
|
||||||
(effectName.includes('major') ? -2 : 0) +
|
|
||||||
(effectName.includes('greater') ? -2 : 0)
|
|
||||||
)
|
|
||||||
modifiers.push({ label: 'Target Arcane Protection', value: effectMod, ignore: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getScaleDistanceMod (token) {
|
|
||||||
const scale = token.actor.system.stats.scale
|
|
||||||
return (scale > 0 ? (scale / 2) : 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDistance (origin, target) {
|
|
||||||
const ray = new Ray(origin, target)
|
|
||||||
const originScale = getScaleDistanceMod(origin)
|
|
||||||
const targetScale = getScaleDistanceMod(target)
|
|
||||||
const distance = shim.canvas.grid.measureDistances([{ ray }], { gridSpaces: true })[0]
|
|
||||||
return distance - (originScale + targetScale)
|
|
||||||
}
|
|
||||||
|
|
||||||
function withinRange (origin, target, range) {
|
|
||||||
const distance = getDistance(origin, target)
|
|
||||||
return range >= distance
|
|
||||||
}
|
|
||||||
|
|
||||||
function _findItem (actor, type, swid) {
|
|
||||||
return actor.items.find(i => i.type === type && i.system.swid === swid)
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcScaleMod (attacker, target) {
|
|
||||||
const attackerScale = attacker.actor.system.stats.scale
|
|
||||||
const targetScale = target.actor.system.stats.scale
|
|
||||||
const attackerHasSwat = !!_findItem(attacker.actor, 'ability', 'swat')
|
|
||||||
let modifier = targetScale - attackerScale
|
|
||||||
if (attackerHasSwat && modifier < 0) {
|
|
||||||
modifier = Math.min(modifier + 4, 0)
|
|
||||||
}
|
|
||||||
return modifier
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcGangup (attacker, target, debug) {
|
|
||||||
debug = (typeof debug === 'undefined') ? false : debug
|
|
||||||
const range = 1.2
|
|
||||||
let modifier = 0
|
|
||||||
if (_findItem(target.actor, 'edge', 'improved-block')) {
|
|
||||||
modifier = -2
|
|
||||||
} else if (_findItem(target.actor, 'edge', 'block')) {
|
|
||||||
modifier = -1
|
|
||||||
}
|
|
||||||
const attackerHasFormationFighter = !!(_findItem(attacker.actor, 'edge', 'formation-fighter'))
|
|
||||||
|
|
||||||
const withinRangeOfToken = shim.canvas.tokens.placeables.filter(t =>
|
|
||||||
t.id !== attacker.id &&
|
|
||||||
t.id !== target.id &&
|
|
||||||
t.actor.system.status.isStunned === false &&
|
|
||||||
t.visible &&
|
|
||||||
withinRange(target, t, range)
|
|
||||||
)
|
|
||||||
const attackerAllies = withinRangeOfToken.filter(
|
|
||||||
t => t.document.disposition === attacker.document.disposition)
|
|
||||||
const targetAllies = withinRangeOfToken.filter(
|
|
||||||
t => t.document.disposition === target.document.disposition &&
|
|
||||||
withinRange(attacker, t, range)
|
|
||||||
)
|
|
||||||
const attackersWithFormationFighter = attackerAllies.filter(
|
|
||||||
t => !!_findItem(t.actor, 'edge', 'formation-fighter'))
|
|
||||||
const attackerCount = attackerAllies.length
|
|
||||||
const attackerFormationBonus = (
|
|
||||||
(attackerCount > 0 && attackerHasFormationFighter ? 1 : 0) +
|
|
||||||
attackersWithFormationFighter.length
|
|
||||||
)
|
|
||||||
const defenderCount = targetAllies.length
|
|
||||||
const gangUp = Math.max(
|
|
||||||
0,
|
|
||||||
Math.min(
|
|
||||||
4,
|
|
||||||
attackerCount + attackerFormationBonus - defenderCount + modifier))
|
|
||||||
if (debug) {
|
|
||||||
log('GANG UP | Attacker:', attacker)
|
|
||||||
log('GANG UP | Target:', target)
|
|
||||||
log('GANG UP | Others within range:', withinRangeOfToken)
|
|
||||||
log('GANG UP | Attacker Allies:', attackerCount)
|
|
||||||
log('GANG UP | Attacker Formation Bonus:', attackerFormationBonus)
|
|
||||||
log('GANG UP | Effective Defender Allies:', defenderCount)
|
|
||||||
log('GANG UP | Target Block Modifier:', modifier)
|
|
||||||
log('GANG UP | Total Bonus:', gangUp)
|
|
||||||
}
|
|
||||||
return gangUp
|
|
||||||
}
|
|
||||||
215
scripts/shim.js
215
scripts/shim.js
@ -1,215 +0,0 @@
|
|||||||
export class CONST {
|
|
||||||
static get SWADE () {
|
|
||||||
return CONFIG.SWADE.CONST
|
|
||||||
}
|
|
||||||
|
|
||||||
static get FOUNDRY () {
|
|
||||||
return foundry.CONST
|
|
||||||
}
|
|
||||||
|
|
||||||
static get WARPGATE () {
|
|
||||||
return warpgate.CONST
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class shim {
|
|
||||||
static get ActiveEffect () {
|
|
||||||
return ActiveEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
static get CONFIG () {
|
|
||||||
return CONFIG
|
|
||||||
}
|
|
||||||
|
|
||||||
static get Actor () {
|
|
||||||
return Actor
|
|
||||||
}
|
|
||||||
|
|
||||||
static get ChatMessage () {
|
|
||||||
return ChatMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
static get game () {
|
|
||||||
return game
|
|
||||||
}
|
|
||||||
|
|
||||||
static get canvas () {
|
|
||||||
return game.canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
static get folders () {
|
|
||||||
return game.folders
|
|
||||||
}
|
|
||||||
|
|
||||||
static get controlled () {
|
|
||||||
return canvas.tokens.controlled
|
|
||||||
}
|
|
||||||
|
|
||||||
static get targets () {
|
|
||||||
return game.user.targets
|
|
||||||
}
|
|
||||||
|
|
||||||
static get user () {
|
|
||||||
return game.user
|
|
||||||
}
|
|
||||||
|
|
||||||
static get users () {
|
|
||||||
return game.users
|
|
||||||
}
|
|
||||||
|
|
||||||
static get notifications () {
|
|
||||||
return ui.notifications
|
|
||||||
}
|
|
||||||
|
|
||||||
static get actors () {
|
|
||||||
return game.actors
|
|
||||||
}
|
|
||||||
|
|
||||||
static get scenes () {
|
|
||||||
return game.scenes
|
|
||||||
}
|
|
||||||
|
|
||||||
static _socket = null
|
|
||||||
|
|
||||||
static get socket () {
|
|
||||||
return shim._socket
|
|
||||||
}
|
|
||||||
|
|
||||||
static get rulesVersion () {
|
|
||||||
if (game.modules.get('swpf-core-rules')?.active) {
|
|
||||||
return 'swpf'
|
|
||||||
}
|
|
||||||
if (game.modules.get('swade-core-rules')?.active) {
|
|
||||||
return 'swade'
|
|
||||||
}
|
|
||||||
return 'system'
|
|
||||||
}
|
|
||||||
|
|
||||||
static mergeObject (...args) {
|
|
||||||
return mergeObject(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getStatus (label, name, favorite = true) {
|
|
||||||
const effect = JSON.parse(JSON.stringify(
|
|
||||||
CONFIG.statusEffects.find(se => se.label === label)))
|
|
||||||
effect.name = ('name' in effect ? effect.name : effect.label)
|
|
||||||
if (!('flags' in effect)) {
|
|
||||||
effect.flags = {}
|
|
||||||
}
|
|
||||||
if (favorite) {
|
|
||||||
if (!('swade' in effect.flags)) {
|
|
||||||
effect.flags.swade = {}
|
|
||||||
}
|
|
||||||
effect.flags.swade.favorite = true
|
|
||||||
}
|
|
||||||
effect.flags.core = { statusId: effect.id }
|
|
||||||
return effect
|
|
||||||
}
|
|
||||||
|
|
||||||
static createEffectDocument (icon, name, durationRounds, changes = null) {
|
|
||||||
if (changes === null) {
|
|
||||||
changes = []
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
icon,
|
|
||||||
name,
|
|
||||||
duration: { rounds: durationRounds },
|
|
||||||
changes,
|
|
||||||
flags: {
|
|
||||||
swade: {
|
|
||||||
favorite: true,
|
|
||||||
expiration: CONST.SWADE.STATUS_EFFECT_EXPIRATION.EndOfTurnPrompt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async applyActiveEffects (token, effectDocuments) {
|
|
||||||
const mutation = {
|
|
||||||
embedded: { ActiveEffect: {} }
|
|
||||||
}
|
|
||||||
const mutateOptions = {
|
|
||||||
permanent: true,
|
|
||||||
description: effectDocuments[effectDocuments.length - 1]?.name
|
|
||||||
}
|
|
||||||
for (const effectDocument of effectDocuments) {
|
|
||||||
mutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
|
|
||||||
}
|
|
||||||
await warpgate.mutate(token.document, mutation, {}, mutateOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
static wait (ms) {
|
|
||||||
return warpgate.wait(ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
static warpgateMenu (menuData, menuOptions) {
|
|
||||||
return warpgate.menu(menuData, menuOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
static warpgateSpawn (...args) {
|
|
||||||
return warpgate.spawn(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static warpgateSpawnAt (...args) {
|
|
||||||
return warpgate.spawnAt(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
static get warpgateUtil () {
|
|
||||||
return warpgate.util
|
|
||||||
}
|
|
||||||
|
|
||||||
static get fearTableHelper () {
|
|
||||||
switch (shim.rulesVersion) {
|
|
||||||
case 'swade': return coreFearDialog
|
|
||||||
case 'swpf': return swpfFearDialog
|
|
||||||
}
|
|
||||||
throw new ReferenceError('No premium module active. No fear table found')
|
|
||||||
}
|
|
||||||
|
|
||||||
static getActorFolderByPath (path) {
|
|
||||||
const names = path.split('/')
|
|
||||||
if (names[0] === '') {
|
|
||||||
names.shift()
|
|
||||||
}
|
|
||||||
let name = names.shift()
|
|
||||||
let folder = shim.folders.filter(
|
|
||||||
f => f.type === 'Actor' && !f.folder
|
|
||||||
).find(f => f.name === name)
|
|
||||||
if (!folder) { return undefined }
|
|
||||||
while (names.length > 0) {
|
|
||||||
name = names.shift()
|
|
||||||
folder = folder.children.find(c => c.folder.name === name)
|
|
||||||
if (!folder) { return undefined }
|
|
||||||
folder = folder.folder
|
|
||||||
}
|
|
||||||
return folder
|
|
||||||
}
|
|
||||||
|
|
||||||
static getActorsInFolder (inFolder) {
|
|
||||||
const prefixStack = ['']
|
|
||||||
const actors = {}
|
|
||||||
const folderStack = [inFolder]
|
|
||||||
while (folderStack.length > 0) {
|
|
||||||
const prefix = prefixStack.shift()
|
|
||||||
const folder = folderStack.shift()
|
|
||||||
for (const actor of folder.contents) {
|
|
||||||
if (shim.user.isGM ||
|
|
||||||
actor.testUserPermission(
|
|
||||||
shim.user, CONST.FOUNDRY.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)
|
|
||||||
) {
|
|
||||||
actors[`${prefix}${actor.name}`] = actor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const child of folder.children) {
|
|
||||||
const newPrefix = `${prefix}${child.folder.name} | `
|
|
||||||
prefixStack.push(newPrefix)
|
|
||||||
folderStack.push(child.folder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return actors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log (...args) {
|
|
||||||
console.log('SWADE MB HELPERS |', ...args)
|
|
||||||
}
|
|
||||||
1283
src/config/token-variants-global-mappings.json
Executable file
1283
src/config/token-variants-global-mappings.json
Executable file
@ -0,0 +1,1283 @@
|
|||||||
|
{
|
||||||
|
"globalMappings": [
|
||||||
|
{
|
||||||
|
"id": "02qnAqdH",
|
||||||
|
"label": "Arcane Protection",
|
||||||
|
"expression": "Arcane Protection (-2) || Arcane Protection (major, -4) || Greater Arcane Protection (-4) || Greater Arcane Protection (major, -6)",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "02qnAqdH",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/Template/Circle/OutPulse/OutPulse_02_Regular_BlueWhite_Burst_600x600.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.18",
|
||||||
|
"scaleY": "0.18",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "NONE",
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Arcane Protection"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rrdhKai4",
|
||||||
|
"label": "Bound or Entangled",
|
||||||
|
"expression": "Bound||Entangled",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "rrdhKai4",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": false,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": true,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/1st_Level/Entangle/Entangle_01_Green_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "0.75",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.27",
|
||||||
|
"scaleY": "0.27",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "NONE",
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shapes": [
|
||||||
|
{
|
||||||
|
"shape": {
|
||||||
|
"type": "rectangle",
|
||||||
|
"x": "0",
|
||||||
|
"y": "0",
|
||||||
|
"width": "100",
|
||||||
|
"height": "100",
|
||||||
|
"radius": "0"
|
||||||
|
},
|
||||||
|
"label": "",
|
||||||
|
"line": {
|
||||||
|
"width": "1",
|
||||||
|
"color": "#000000",
|
||||||
|
"alpha": "1"
|
||||||
|
},
|
||||||
|
"fill": {
|
||||||
|
"color": "#ffffff",
|
||||||
|
"alpha": "1",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repeating": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Bound or Entangled"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5EAavaok",
|
||||||
|
"label": "Conviction",
|
||||||
|
"expression": "actor.system.details.conviction.active=\"true\"",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "5EAavaok",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": true,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/1st_Level/Bless/Bless_01_Regular_Yellow_Loop_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": ".4",
|
||||||
|
"scaleY": ".4",
|
||||||
|
"angle": "1",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "DropShadowFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"rotation": 45,
|
||||||
|
"distance": 0,
|
||||||
|
"color": "#000000",
|
||||||
|
"alpha": 0.86,
|
||||||
|
"shadowOnly": false,
|
||||||
|
"blur": 2.3,
|
||||||
|
"quality": 0
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Conviction"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gRwsZcZK",
|
||||||
|
"label": "Deflection",
|
||||||
|
"expression": "Deflection (melee) || Deflection (range) || Deflection (all)",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "gRwsZcZK",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": true,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/Marker/MarkerShield_03_Regular_Green_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.45",
|
||||||
|
"scaleY": "0.45",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "OutlineFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"thickness": 1,
|
||||||
|
"color": "#000000",
|
||||||
|
"quality": 0.1
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Deflection"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "toTYr3DQ",
|
||||||
|
"label": "Distracted",
|
||||||
|
"expression": "Distracted && \\!Stunned",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "toTYr3DQ",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": true,
|
||||||
|
"duration": "30000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": true,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/Particles/ParticlesInward02_04_Regular_GreenYellow_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": ".3",
|
||||||
|
"scaleY": ".3",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "OutlineFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"thickness": 1,
|
||||||
|
"color": "#000000",
|
||||||
|
"quality": 0
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Distracted"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "KtequnXd",
|
||||||
|
"label": "Flying",
|
||||||
|
"expression": "Flying",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {
|
||||||
|
"tv_script": {
|
||||||
|
"onApply": "",
|
||||||
|
"onRemove": "",
|
||||||
|
"tmfxPreset": "dropshadow"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overlay": false,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "KtequnXd",
|
||||||
|
"label": "Flying"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eO68BGDl",
|
||||||
|
"label": "Glow",
|
||||||
|
"expression": "Glow",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {
|
||||||
|
"light": {
|
||||||
|
"dim": 0.25,
|
||||||
|
"bright": 0,
|
||||||
|
"color": "#1c71d8",
|
||||||
|
"alpha": 0.4,
|
||||||
|
"animation": {
|
||||||
|
"type": "sunburst",
|
||||||
|
"speed": 3,
|
||||||
|
"intensity": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "eO68BGDl",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": true,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": false
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Cantrip/Dancing_Lights/DancingLights_01_Yellow_200x200.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.15",
|
||||||
|
"scaleY": "0.15",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0.52",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "NONE",
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Glow"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1po9hq1m",
|
||||||
|
"label": "Protection",
|
||||||
|
"expression": "Protection",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "1po9hq1m",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/1st_Level/Shield/Shield_01_Regular_Blue_Loop_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": ".35",
|
||||||
|
"scaleY": ".35",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "NONE",
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Protection"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mwFtNKpD",
|
||||||
|
"label": "Shaken",
|
||||||
|
"expression": "Shaken",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "mwFtNKpD",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "30000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/Template/Circle/TemplateStunCircle_01_Regular_Purple_800x800.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "0.5",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": ".15",
|
||||||
|
"scaleY": ".15",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "DropShadowFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"rotation": 45,
|
||||||
|
"distance": 7,
|
||||||
|
"color": "#000000",
|
||||||
|
"alpha": 0.84,
|
||||||
|
"shadowOnly": false,
|
||||||
|
"blur": 2,
|
||||||
|
"quality": 0
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "Shaken",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Shaken"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BP0Xx8wD",
|
||||||
|
"label": "Shroud",
|
||||||
|
"expression": "Shroud",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "BP0Xx8wD",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": true,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/2nd_Level/Darkness/Opacities/Darkness_01_Black_75OPA_600x600.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.17",
|
||||||
|
"scaleY": "0.17",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "NONE",
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#ffffff",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Shroud"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nOfPMsQp",
|
||||||
|
"label": "Stunned",
|
||||||
|
"expression": "Stunned",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": false,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "nOfPMsQp",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5000",
|
||||||
|
"clockwise": true
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/UI/IconStun_01_Regular_Purple_200x200.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": "0.4",
|
||||||
|
"scaleY": "0.4",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "OutlineFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"thickness": 1,
|
||||||
|
"color": "#000000",
|
||||||
|
"quality": 0.1
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Stunned"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "J4GrRaxL",
|
||||||
|
"label": "Vulnerable",
|
||||||
|
"expression": "Vulnerable && \\!Stunned",
|
||||||
|
"codeExp": "",
|
||||||
|
"imgName": "",
|
||||||
|
"imgSrc": "",
|
||||||
|
"priority": 50,
|
||||||
|
"config": {},
|
||||||
|
"overlay": true,
|
||||||
|
"alwaysOn": true,
|
||||||
|
"disabled": false,
|
||||||
|
"overlayConfig": {
|
||||||
|
"id": "J4GrRaxL",
|
||||||
|
"html": {
|
||||||
|
"template": "",
|
||||||
|
"style": "",
|
||||||
|
"listeners": ""
|
||||||
|
},
|
||||||
|
"parentID": "",
|
||||||
|
"ui": false,
|
||||||
|
"underlay": false,
|
||||||
|
"bottom": false,
|
||||||
|
"top": false,
|
||||||
|
"inheritTint": false,
|
||||||
|
"linkRotation": true,
|
||||||
|
"animation": {
|
||||||
|
"relative": false,
|
||||||
|
"rotate": false,
|
||||||
|
"duration": "5200",
|
||||||
|
"clockwise": false
|
||||||
|
},
|
||||||
|
"linkMirror": true,
|
||||||
|
"linkScale": false,
|
||||||
|
"linkDimensionsX": true,
|
||||||
|
"linkDimensionsY": true,
|
||||||
|
"linkOpacity": false,
|
||||||
|
"linkStageScale": false,
|
||||||
|
"loop": true,
|
||||||
|
"playOnce": false,
|
||||||
|
"img": "modules/JB2A_DnD5e/Library/Generic/Marker/MarkerShieldCracked_03_Regular_Purple_400x400.webm",
|
||||||
|
"repeating": false,
|
||||||
|
"alpha": "1",
|
||||||
|
"tint": "",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"width": "1",
|
||||||
|
"height": "1",
|
||||||
|
"scaleX": ".36",
|
||||||
|
"scaleY": ".36",
|
||||||
|
"angle": "0",
|
||||||
|
"pOffsetX": "",
|
||||||
|
"pOffsetY": "",
|
||||||
|
"offsetX": "0",
|
||||||
|
"offsetY": "0",
|
||||||
|
"anchor": {
|
||||||
|
"x": 0.5,
|
||||||
|
"y": 0.5
|
||||||
|
},
|
||||||
|
"filter": "OutlineFilter",
|
||||||
|
"filterOptions": {
|
||||||
|
"thickness": 1,
|
||||||
|
"color": "#000000",
|
||||||
|
"quality": 0.1
|
||||||
|
},
|
||||||
|
"alwaysVisible": false,
|
||||||
|
"limitedToOwner": false,
|
||||||
|
"limitedUsers": [],
|
||||||
|
"limitOnHover": false,
|
||||||
|
"limitOnHighlight": false,
|
||||||
|
"limitOnControl": false,
|
||||||
|
"limitOnHUD": false,
|
||||||
|
"limitOnTarget": false,
|
||||||
|
"limitOnAnyTarget": false,
|
||||||
|
"limitOnEffect": "",
|
||||||
|
"limitOnProperty": "",
|
||||||
|
"text": {
|
||||||
|
"text": "",
|
||||||
|
"repeating": false,
|
||||||
|
"fontFamily": "Signika",
|
||||||
|
"fill": "#FFFFFF",
|
||||||
|
"interpolateColor": {
|
||||||
|
"color2": "",
|
||||||
|
"prc": ""
|
||||||
|
},
|
||||||
|
"fontSize": "36",
|
||||||
|
"fontWeight": "normal",
|
||||||
|
"align": "center",
|
||||||
|
"letterSpacing": "0",
|
||||||
|
"dropShadow": "true",
|
||||||
|
"strokeThickness": "1",
|
||||||
|
"stroke": "#111111",
|
||||||
|
"wordWrap": false,
|
||||||
|
"wordWrapWidth": "200",
|
||||||
|
"breakWords": false,
|
||||||
|
"maxHeight": "0",
|
||||||
|
"curve": {
|
||||||
|
"angle": "0",
|
||||||
|
"radius": "0",
|
||||||
|
"invert": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"effect": "",
|
||||||
|
"imgLinked": false,
|
||||||
|
"interactivity": [],
|
||||||
|
"label": "Vulnerable"
|
||||||
|
},
|
||||||
|
"group": "Default",
|
||||||
|
"i": 11
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
src/lang/en.json
Normal file
6
src/lang/en.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"mbhelpers.settings.powerActorsCompendiumName": "Power Actors Compendium",
|
||||||
|
"mbhelpers.settings.powerActorsCompendiumHint": "Identifier of a compendium that holds all the actor helpers for powers. See the documentation for details on the structure of this compendium.",
|
||||||
|
"mbhelpers.settings.powersJournalName": "Powers Journal",
|
||||||
|
"mbhelpers.settings.powersJournalHint": "UUID of a helper journal for actor-based powers (summonables and morphables)."
|
||||||
|
}
|
||||||
@ -1,21 +1,25 @@
|
|||||||
{
|
{
|
||||||
"id": "swade-mb-helpers",
|
"id": "swade-mb-helpers",
|
||||||
"title": "SWADE Helpers (MB)",
|
"title": "SWADE Helpers (MB)",
|
||||||
"version": "2.3.5",
|
"description": "Mike's collection of SWADE helpers",
|
||||||
"description": "Mike's collection of swade helpers",
|
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Mike"
|
"name": "Mike",
|
||||||
|
"flags": {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
||||||
|
"version": "4.1.0",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "11",
|
"minimum": "13",
|
||||||
"verified": "11"
|
"verified": "13"
|
||||||
},
|
},
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
"scripts/module.js"
|
"module/swade-mb-helpers.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/swade-mb-helpers.css"
|
||||||
],
|
],
|
||||||
"socket": true,
|
|
||||||
"packs": [
|
"packs": [
|
||||||
{
|
{
|
||||||
"name": "module-docs",
|
"name": "module-docs",
|
||||||
@ -61,6 +65,17 @@
|
|||||||
"ASSISTANT": "OWNER"
|
"ASSISTANT": "OWNER"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "power-actors",
|
||||||
|
"label": "SWADE MB Example Power Actors",
|
||||||
|
"path": "packs/power-actors",
|
||||||
|
"type": "Actor",
|
||||||
|
"system": "swade",
|
||||||
|
"ownership": {
|
||||||
|
"PLAYER": "OBSERVER",
|
||||||
|
"ASSISTANT": "OWNER"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "swade-mb-gear",
|
"name": "swade-mb-gear",
|
||||||
"label": "SWADE MB Gear",
|
"label": "SWADE MB Gear",
|
||||||
@ -84,7 +99,8 @@
|
|||||||
"helper-macros",
|
"helper-macros",
|
||||||
"helper-actors",
|
"helper-actors",
|
||||||
"Common Actions",
|
"Common Actions",
|
||||||
"swade-mb-gear"
|
"swade-mb-gear",
|
||||||
|
"power-actors"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -95,31 +111,29 @@
|
|||||||
"type": "system",
|
"type": "system",
|
||||||
"manifest": "https://gitlab.com/api/v4/projects/16269883/packages/generic/swade/latest/system.json",
|
"manifest": "https://gitlab.com/api/v4/projects/16269883/packages/generic/swade/latest/system.json",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"verified": "2.2.5"
|
"minimum": "5.1.0",
|
||||||
|
"verified": "5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requires": [
|
"requires": [
|
||||||
{
|
{
|
||||||
"id": "warpgate",
|
"id": "socketlib",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"manifest": "https://github.com/trioderegion/warpgate/releases/latest/download/module.json",
|
"compatibility": {}
|
||||||
"compatibility": {
|
|
||||||
"verified": "1.16.2"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "socketlib",
|
"id": "tcal",
|
||||||
|
"type": "module",
|
||||||
|
"compatibility": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sequencer",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"compatibility": {}
|
"compatibility": {}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"recommends": [
|
"recommends": [
|
||||||
{
|
|
||||||
"id": "token-variants",
|
|
||||||
"type": "module",
|
|
||||||
"compatibility": {}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "torch",
|
"id": "torch",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -127,13 +141,25 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "JB2A_DnD5e",
|
"id": "JB2A_DnD5e",
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"compatibility": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "visual-active-effects",
|
||||||
|
"type": "module",
|
||||||
|
"compatibility": {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
"languages": [
|
||||||
"manifest": "https://git.bloy.org/foundryvtt/swade-mb-helpers/raw/branch/main/module.json",
|
{
|
||||||
"download": "https://git.bloy.org/foundryvtt/swade-mb-helpers/archive/main.zip",
|
"lang": "en",
|
||||||
"license": "./LICENSE",
|
"name": "English",
|
||||||
"readme": "./README.md"
|
"path": "lang/en.json",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"socket": true,
|
||||||
|
"manifest": "https://git.bloy.org/foundryvtt/swade-mb-helpers/releases/download/latest/module.json",
|
||||||
|
"download": "https://git.bloy.org/foundryvtt/swade-mb-helpers/releases/download/latest/swade-mb-helpers.zip"
|
||||||
}
|
}
|
||||||
23
src/module/api.js
Normal file
23
src/module/api.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { log, moduleHelpers } from './globals.js';
|
||||||
|
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js';
|
||||||
|
import { powers, PowerClasses, powerEffectsMenu } from './powers/powers.js';
|
||||||
|
import { setSummonCosts } from './powers/summonSupport.js';
|
||||||
|
|
||||||
|
export class api {
|
||||||
|
static registerFunctions() {
|
||||||
|
log('SWADE MB Helpers initialized');
|
||||||
|
const moduleName = 'swade-mb-helpers';
|
||||||
|
const mbSwadeApi = {
|
||||||
|
fearTable: moduleHelpers.fearTableHelper,
|
||||||
|
powerEffects: powers,
|
||||||
|
PowerClasses,
|
||||||
|
powerEffectsMenu,
|
||||||
|
requestFearRollFromTokens,
|
||||||
|
requestRollFromTokens,
|
||||||
|
rulesVersion: moduleHelpers.rulesVersion,
|
||||||
|
setSummonCosts,
|
||||||
|
};
|
||||||
|
game.modules.get(moduleName).api = mbSwadeApi;
|
||||||
|
game.mbSwade = mbSwadeApi;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/module/globals.js
Normal file
95
src/module/globals.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
export const moduleName = 'swade-mb-helpers';
|
||||||
|
|
||||||
|
export const settingKeys = {
|
||||||
|
powerActorsCompendium: 'powerActorsCompendium',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function log(...args) {
|
||||||
|
console.log('SWADE MB HELPERS |', ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class moduleHelpers {
|
||||||
|
static _socket = null;
|
||||||
|
|
||||||
|
static get socket() {
|
||||||
|
return moduleHelpers._socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSetting(key) {
|
||||||
|
return game.settings.get(moduleName, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setSetting(key, value) {
|
||||||
|
return game.settings.get(moduleName, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async registerSetting(key, metadata) {
|
||||||
|
return game.settings.register(moduleName, key, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get rulesVersion() {
|
||||||
|
if (game.modules.get('swpf-core-rules')?.active) {
|
||||||
|
return 'swpf';
|
||||||
|
}
|
||||||
|
if (game.modules.get('swade-core-rules')?.active) {
|
||||||
|
return 'swade';
|
||||||
|
}
|
||||||
|
return 'system';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get useVAE() {
|
||||||
|
return !!game.modules.get('visual-active-effects')?.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getActorFolderByPath(path) {
|
||||||
|
const names = path.split('/');
|
||||||
|
if (names[0] === '') {
|
||||||
|
names.shift();
|
||||||
|
}
|
||||||
|
let name = names.shift();
|
||||||
|
let folder = game.folders.filter((f) => f.type === 'Actor' && !f.folder).find((f) => f.name === name);
|
||||||
|
if (!folder) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
while (names.length > 0) {
|
||||||
|
name = names.shift();
|
||||||
|
folder = folder.children.find((c) => c.folder.name === name);
|
||||||
|
if (!folder) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
folder = folder.folder;
|
||||||
|
}
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getActorsInFolder(inFolder) {
|
||||||
|
const prefixStack = [''];
|
||||||
|
const actors = {};
|
||||||
|
const folderStack = [inFolder];
|
||||||
|
while (folderStack.length > 0) {
|
||||||
|
const prefix = prefixStack.shift();
|
||||||
|
const folder = folderStack.shift();
|
||||||
|
for (const actor of folder.contents) {
|
||||||
|
if (game.user.isGM || actor.testUserPermission(game.user, foundry.CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)) {
|
||||||
|
actors[`${prefix}${actor.name}`] = actor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const child of folder.children) {
|
||||||
|
const newPrefix = `${prefix}${child.folder.name} | `;
|
||||||
|
prefixStack.push(newPrefix);
|
||||||
|
folderStack.push(child.folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actors;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get fearTableHelper() {
|
||||||
|
switch (moduleHelpers.rulesVersion) {
|
||||||
|
case 'swade':
|
||||||
|
return game.swadeCore.macros.coreFearDialog; // defined as global by the swade module
|
||||||
|
case 'swpf':
|
||||||
|
return game.swpfCore.macros.coreFearDialog; // defined as global by the swpf module
|
||||||
|
}
|
||||||
|
throw new ReferenceError('No premium module active. No fear table found');
|
||||||
|
}
|
||||||
|
}
|
||||||
206
src/module/helpers.js
Normal file
206
src/module/helpers.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import { moduleHelpers } from './globals.js';
|
||||||
|
|
||||||
|
export async function requestFearRollFromTokens(tokens, options = {}) {
|
||||||
|
// tokens: list of tokens to request the roll from
|
||||||
|
// options:
|
||||||
|
// title: tile for the roll dialog. Will have "- {{ token name }}" appended
|
||||||
|
// flavour: flavor text for the roll card. Defaults to title
|
||||||
|
// fear: value of the fear modifier. Defaults to 0. Positive number.
|
||||||
|
const requestingUser = game.user;
|
||||||
|
const title = options?.title || `${requestingUser.name} requests a Fear check`;
|
||||||
|
const flavour = options?.flavour || options?.flavor || title;
|
||||||
|
const fear = options.fear || 0;
|
||||||
|
const rollOpts = {
|
||||||
|
title,
|
||||||
|
flavour,
|
||||||
|
mods: [{ label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false }],
|
||||||
|
};
|
||||||
|
return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function firstOwner(doc) {
|
||||||
|
// lifted from warpgate
|
||||||
|
// https://github.com/trioderegion/warpgate/blob/master/src/scripts/module.js
|
||||||
|
if (!doc) return undefined;
|
||||||
|
const corrected =
|
||||||
|
doc instanceof TokenDocument
|
||||||
|
? doc.actor
|
||||||
|
: doc instanceof foundry.canvas.placeables.Token
|
||||||
|
? doc.document.actor
|
||||||
|
: doc;
|
||||||
|
const permissionObject = foundry.utils.getProperty(corrected ?? {}, 'ownership');
|
||||||
|
const playerOwners = Object.entries(permissionObject)
|
||||||
|
.filter(([id, level]) => !game.users.get(id)?.isGM && game.users.get(id)?.active && level === 3)
|
||||||
|
.map(([id]) => id);
|
||||||
|
if (playerOwners.length > 0) {
|
||||||
|
return game.users.get(playerOwners[0]);
|
||||||
|
}
|
||||||
|
return firstGM();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function firstGM() {
|
||||||
|
// lifted from warpgate
|
||||||
|
// https://github.com/trioderegion/warpgate/blob/master/src/scripts/module.js
|
||||||
|
return game.users?.find((u) => u.isGM && u.active);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestRollFromTokens(tokens, rollType, rollDesc, options = {}) {
|
||||||
|
// tokens: list of tokens to request a roll from
|
||||||
|
// rollType: 'attribute' or 'skill
|
||||||
|
// rollDesc: name of attribute or skill
|
||||||
|
// options:
|
||||||
|
// title: title for the roll dialog. Will have "- {{ token name }}"
|
||||||
|
// appended
|
||||||
|
// flavour: flavor text for the roll card. Defaults to title
|
||||||
|
// targetNumber: defaults to 4
|
||||||
|
// mods: list of modifiers {label: "", value: 0, ignore: false}
|
||||||
|
// modCallback: callback function that takes a token and returns a list of
|
||||||
|
// modifiers in the same format as modifiers, above
|
||||||
|
const requestingUser = game.user;
|
||||||
|
const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll`;
|
||||||
|
const flavour = options?.flavour || options?.flavor || title;
|
||||||
|
const targetNumber = options?.targetNumber || 4;
|
||||||
|
const promises = [];
|
||||||
|
for (const token of tokens) {
|
||||||
|
const owner = firstOwner(token.document);
|
||||||
|
const rollOpts = {
|
||||||
|
title: `${title} - ${token.name}`,
|
||||||
|
targetNumber,
|
||||||
|
flavour,
|
||||||
|
};
|
||||||
|
const additionalMods = [];
|
||||||
|
if ('mods' in options) {
|
||||||
|
for (const mod of options.mods) {
|
||||||
|
additionalMods.push(mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('modCallback' in options) {
|
||||||
|
const tokenMods = await options.modCallback(token);
|
||||||
|
for (const tm of tokenMods) {
|
||||||
|
additionalMods.push(tm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (additionalMods.length > 0) {
|
||||||
|
rollOpts.additionalMods = additionalMods;
|
||||||
|
}
|
||||||
|
promises.push(
|
||||||
|
moduleHelpers.socket.executeAsUser(
|
||||||
|
requestTokenRoll,
|
||||||
|
owner.id,
|
||||||
|
token.scene.id,
|
||||||
|
token.id,
|
||||||
|
rollType,
|
||||||
|
rollDesc,
|
||||||
|
rollOpts,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const results = (await Promise.allSettled(promises)).map((r) => r.value);
|
||||||
|
const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}`;
|
||||||
|
const messageData = {
|
||||||
|
flavor: flavour,
|
||||||
|
speaker: { alias: 'Requested Roll Results' },
|
||||||
|
whisper: [...ChatMessage.getWhisperRecipients('GM'), requestingUser],
|
||||||
|
content: `<p>Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:</p>
|
||||||
|
<table><thead><tr><th>Token</th><th>Roll</th><th>Result</th></tr></thead><tbody>`,
|
||||||
|
};
|
||||||
|
for (const result of results) {
|
||||||
|
const token = game.scenes.get(result.sceneId).tokens.get(result.tokenId);
|
||||||
|
const roll =
|
||||||
|
result.result instanceof CONFIG.Dice.SwadeRoll
|
||||||
|
? result.result
|
||||||
|
: CONFIG.Dice[result.result.class].fromData(result.result);
|
||||||
|
roll.targetNumber = targetNumber;
|
||||||
|
let textResult = '';
|
||||||
|
if (roll.successes === -1) {
|
||||||
|
textResult = 'CRITICAL FAILURE';
|
||||||
|
} else if (roll.successes === 0) {
|
||||||
|
textResult = 'failed';
|
||||||
|
} else if (roll.successes === 1) {
|
||||||
|
textResult = 'success';
|
||||||
|
} else {
|
||||||
|
textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}`;
|
||||||
|
}
|
||||||
|
messageData.content +=
|
||||||
|
'<tr>' +
|
||||||
|
`<th>${token.name}</th>` +
|
||||||
|
`<td>${roll ? roll.total : '<i>Canceled</i>'}</td>` +
|
||||||
|
`<td>${textResult}</td>` +
|
||||||
|
'</tr>';
|
||||||
|
}
|
||||||
|
messageData.content += '</tbody></table>';
|
||||||
|
ChatMessage.create(messageData, {});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestTokenRoll(sceneId, tokenId, rollType, rollDesc, options) {
|
||||||
|
const scene = game.scenes.get(sceneId);
|
||||||
|
const token = scene.tokens.get(tokenId);
|
||||||
|
let rollFunc = 'rollAttribute';
|
||||||
|
let rollId = rollDesc.toLowerCase();
|
||||||
|
if (rollType === 'skill') {
|
||||||
|
rollFunc = 'rollSkill';
|
||||||
|
rollId = token.actor.items
|
||||||
|
.filter((i) => i.type === 'skill')
|
||||||
|
.find((i) => i.system.swid === rollDesc.toLowerCase() || i.name.toLowerCase() === rollDesc.toLowerCase())?.id;
|
||||||
|
}
|
||||||
|
const result = await token.actor[rollFunc](rollId, options);
|
||||||
|
return { sceneId, tokenId, result };
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getSceneToken(sceneId, tokenId) {
|
||||||
|
const scene = game.scenes.get(sceneId);
|
||||||
|
const token = scene.tokens.get(tokenId);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addActiveEffectsToToken(sceneId, tokenId, effectDocuments) {
|
||||||
|
const token = _getSceneToken(sceneId, tokenId);
|
||||||
|
await token.actor.createEmbeddedDocuments('ActiveEffect', effectDocuments);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteActiveEffectsFromToken(sceneId, tokenId, effectIds) {
|
||||||
|
const token = _getSceneToken(sceneId, tokenId);
|
||||||
|
await token.actor.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addItemsToToken(sceneId, tokenId, itemDocuments) {
|
||||||
|
const token = _getSceneToken(sceneId, tokenId);
|
||||||
|
await token.actor.createEmbeddedDocuments('Item', itemDocuments, { renderSheet: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteItemsFromActor(actorUuid, itemIds) {
|
||||||
|
const actor = await fromUuid(actorUuid);
|
||||||
|
await actor.deleteEmbeddedDocuments('Item', itemIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateOwnedToken(sceneId, tokenId, updates, options = {}) {
|
||||||
|
const token = _getSceneToken(sceneId, tokenId);
|
||||||
|
return token.update(updates, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteToken(sceneId, tokenId) {
|
||||||
|
const token = _getSceneToken(sceneId, tokenId);
|
||||||
|
return token.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SwadeVAEbuttons(effect, buttons) {
|
||||||
|
if (['Bound', 'Entangled'].includes(effect?.name)) {
|
||||||
|
buttons.push({
|
||||||
|
label: 'Break Free (Athletics)',
|
||||||
|
callback: function () {
|
||||||
|
const skillId = effect.parent.items.find((i) => i.type === 'skill' && i.system.swid === 'athletics')?.id;
|
||||||
|
effect.parent.rollSkill(skillId, { flavor: 'Breaking Free' });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
buttons.push({
|
||||||
|
label: 'Break Free (Strength -2)',
|
||||||
|
callback: function () {
|
||||||
|
effect.parent.rollAttribute('strength', {
|
||||||
|
flavor: 'Breaking Free',
|
||||||
|
additionalMods: [{ label: 'Breaking Free with Strength', value: -2 }],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/module/powers/arcaneProtection.js
Normal file
63
src/module/powers/arcaneProtection.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class ArcaneProtectionEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Arcane Protection';
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/defensive/shield-barrier-flaming-pentagon-blue.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAdditionalRecipients() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientCost() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push({
|
||||||
|
name: 'Greater Arcane Protection',
|
||||||
|
id: 'greater',
|
||||||
|
value: 2,
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _penaltyAmount() {
|
||||||
|
return (this.data.raise ? -4 : -2) + (this.data.greater ? -2 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let text = super.description;
|
||||||
|
text += `<p>Hostile powers are at ${this._penaltyAmount} when
|
||||||
|
targeting or damaging the affected character.</p>`;
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
const greater = this.data.greater;
|
||||||
|
const raise = this.data.raise;
|
||||||
|
const amount = this._penaltyAmount;
|
||||||
|
return `${greater ? 'Greater ' : ''}Arcane Protection (${raise ? 'major, ' : ''}${amount})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
196
src/module/powers/balefulPolymorph.js
Normal file
196
src/module/powers/balefulPolymorph.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { moduleHelpers, moduleName } from '../globals.js';
|
||||||
|
import { firstOwner, updateOwnedToken } from '../helpers.js';
|
||||||
|
import { ActorFolderEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BalefulPolymorphEffect extends ActorFolderEffect {
|
||||||
|
get actorFolderBase() {
|
||||||
|
return 'Morphables';
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'Baleful Polymorph';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/control/silhouette-hold-change-blue.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return this.data.duration ? 50 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get oneTarget() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRaisable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasRange() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
actorValue(actor) {
|
||||||
|
const size = actor.system.stats.size;
|
||||||
|
const targetSize = this.targets[0].actor.system.stats.size;
|
||||||
|
return Math.abs(targetSize - size);
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
return [
|
||||||
|
...super.modifiers,
|
||||||
|
{
|
||||||
|
name: 'Duration',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 2,
|
||||||
|
id: 'duration',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Victim critical failure',
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
id: 'critfail',
|
||||||
|
epic: false,
|
||||||
|
value: 0,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseValues() {
|
||||||
|
await super.parseValues();
|
||||||
|
this.target = this?.targets?.[0];
|
||||||
|
this.data.actorUpdates = {
|
||||||
|
name: `${this.target.actor.name} (${this.targetActor.name} form)`,
|
||||||
|
system: {
|
||||||
|
wildcard: this.target.actor.system.wildcard,
|
||||||
|
attributes: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const attrList = ['spirit'];
|
||||||
|
if (!this.data.critfail) {
|
||||||
|
attrList.push('smarts');
|
||||||
|
}
|
||||||
|
for (const stat of attrList) {
|
||||||
|
this.data.actorUpdates.system.attributes[stat] = {
|
||||||
|
die: this.target.actor.system.attributes[stat].die,
|
||||||
|
'wild-die': this.target.actor.system.attributes[stat]['wild-die'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.data.tokenUpdates = {
|
||||||
|
flags: {
|
||||||
|
[moduleName]: {
|
||||||
|
'shapeChange.srcTokenUuid': this.target.document.uuid,
|
||||||
|
'shapeChange.srcTokenId': this.target.document.id,
|
||||||
|
'shapeChange.srcTokenSceneId': this.target.scene.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actorLink: false,
|
||||||
|
name: `${this.target.name} (${this.targetActor.prototypeToken.name} form)`,
|
||||||
|
disposition: this.target.document.disposition,
|
||||||
|
sight: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.data.embeddedUpdates = {
|
||||||
|
ActiveEffect: {},
|
||||||
|
Item: {},
|
||||||
|
};
|
||||||
|
for (const effect of this.target.actor.effects) {
|
||||||
|
const doc = foundry.utils.deepClone(await this.target.actor.getEmbeddedDocument('ActiveEffect', effect.id));
|
||||||
|
this.data.embeddedUpdates.ActiveEffect[effect.name] = doc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawn() {
|
||||||
|
const target = this.target.document;
|
||||||
|
const size = target.parent.dimensions.size;
|
||||||
|
const protoWidth = this.targetActor.prototypeToken.width;
|
||||||
|
const protoHeight = this.targetActor.prototypeToken.height;
|
||||||
|
this.targetTokenDoc.updateSource({
|
||||||
|
x: target.x - ((protoWidth - target.width) * size) / 2,
|
||||||
|
y: target.y - ((protoHeight - target.height) * size) / 2,
|
||||||
|
elevation: target.elevation,
|
||||||
|
hidden: target.hidden,
|
||||||
|
});
|
||||||
|
return this.source.scene.createEmbeddedDocuments('Token', [this.targetTokenDoc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async apply() {
|
||||||
|
await super.apply();
|
||||||
|
const maintainDoc = await this.createMaintainEffect(this.data.maintId);
|
||||||
|
maintainDoc.flags[moduleName].targetIds = this.data.spawned.map((t) => t.id);
|
||||||
|
maintainDoc.flags[moduleName].shapeChangeSourceId = this.target.id;
|
||||||
|
maintainDoc.flags[moduleName].shapeChangeTempTokenId = this.data.spawned[0].id;
|
||||||
|
let maintainer = this.source;
|
||||||
|
if (this.source.id === this.target.id) {
|
||||||
|
maintainer = this.data.spawned[0];
|
||||||
|
}
|
||||||
|
await this.applyActiveEffects(maintainer, [maintainDoc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
return `Baleful Polymorph into ${this.targetActor.prototypeToken.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get spawnUpdates() {
|
||||||
|
const updates = super.spawnUpdates;
|
||||||
|
foundry.utils.mergeObject(updates.actor, this.data.actorUpdates);
|
||||||
|
foundry.utils.mergeObject(updates.token, this.data.tokenUpdates);
|
||||||
|
foundry.utils.mergeObject(updates.embedded, this.data.embeddedUpdates);
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sideEffects() {
|
||||||
|
const owner = firstOwner(this.target);
|
||||||
|
moduleHelpers.socket.executeAsUser(
|
||||||
|
updateOwnedToken,
|
||||||
|
owner.id,
|
||||||
|
this.target.document.parent.id,
|
||||||
|
this.target.document.id,
|
||||||
|
{ hidden: true, x: 0, y: 0 },
|
||||||
|
{ animate: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let desc = super.description;
|
||||||
|
desc += `<p>On losing an opposed roll vs the victim's Spirit, the victim
|
||||||
|
is transformed, taking the form and abilities of a <em>${this.targetActor.name}</em>
|
||||||
|
but retaining their ${this.data.critfail ? '' : 'Smarts and '}Spirit. `;
|
||||||
|
if (this.data.critfail) {
|
||||||
|
desc += `The victim believes they <strong>are</strong> the animal for
|
||||||
|
the duration of the power.`;
|
||||||
|
}
|
||||||
|
desc += `</p><p>The victim may attempt to shake off the effect with a Spirit
|
||||||
|
roll at ${this.data.raise ? -4 : -2} at the end of subsequent turns.`;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
get primaryEffectButtons() {
|
||||||
|
const buttons = super.primaryEffectButtons;
|
||||||
|
const modvalue = this.data.raise ? -4 : -2;
|
||||||
|
const mods = [];
|
||||||
|
mods.push({ label: 'Strong', value: modvalue });
|
||||||
|
buttons.push({
|
||||||
|
label: `Shake off (Spirit ${modvalue})`,
|
||||||
|
type: 'trait',
|
||||||
|
rollType: 'attribute',
|
||||||
|
rollDesc: 'Spirit',
|
||||||
|
flavor: 'Success shakes off the effects of sloth',
|
||||||
|
mods,
|
||||||
|
});
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/module/powers/banish.js
Normal file
92
src/module/powers/banish.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
import { requestRollFromTokens } from '../helpers.js';
|
||||||
|
|
||||||
|
export class BanishEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Banish';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/control/sihouette-hold-beam-green.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRaisable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAoe() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push({
|
||||||
|
type: 'number',
|
||||||
|
default: 4,
|
||||||
|
name: 'Opposed Target Number',
|
||||||
|
id: 'tn',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
value: 0,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
type: 'select',
|
||||||
|
default: 'none',
|
||||||
|
name: 'Area of Effect',
|
||||||
|
id: 'aoe',
|
||||||
|
epic: true,
|
||||||
|
choices: {
|
||||||
|
none: 'None',
|
||||||
|
sbt: 'Small Blast Template',
|
||||||
|
mbt: 'Medium Blast Template',
|
||||||
|
lbt: 'Large Blast Template',
|
||||||
|
},
|
||||||
|
effects: { none: null, sbt: null, mbt: null, lbt: null },
|
||||||
|
values: { none: 0, sbt: 1, mbt: 2, lbt: 3 },
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sideEffects() {
|
||||||
|
await super.sideEffects();
|
||||||
|
const rollOpts = {
|
||||||
|
title: 'Spirit roll to resist banishment',
|
||||||
|
flavor: 'Roll Spirit to resist banishment!',
|
||||||
|
targetNumber: this.data.tn,
|
||||||
|
};
|
||||||
|
await requestRollFromTokens(this.targets, 'ability', 'spirit', rollOpts);
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return (
|
||||||
|
super.description +
|
||||||
|
`<p>An opposed roll of the caster's skill vs the target's Spirit.
|
||||||
|
<strong>Success:</strong> Shaken, <strong>each Raise:</strong> 1 Wound
|
||||||
|
Incapacitation results in banishment to home plane.</p>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get chatMessageEffects() {
|
||||||
|
const list = super.chatMessageEffects;
|
||||||
|
if (this.data.aoe !== 'none') {
|
||||||
|
list.push(this.data.aoe.toUpperCase());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/module/powers/barrier.js
Normal file
120
src/module/powers/barrier.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
export class BarrierEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Barrier';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/environment/settlement/fence-stone-brick.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDamaging() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push({
|
||||||
|
type: 'select',
|
||||||
|
name: 'Damage',
|
||||||
|
id: 'damage',
|
||||||
|
epic: false,
|
||||||
|
choices: {
|
||||||
|
none: 'None',
|
||||||
|
damage: 'Damage',
|
||||||
|
immaterial: 'Damage (immaterial)',
|
||||||
|
deadly: '⭐ Deadly',
|
||||||
|
},
|
||||||
|
effects: { none: null, damage: null, immaterial: null, deadly: null },
|
||||||
|
values: { none: 0, damage: 1, immaterial: 0, deadly: 2 },
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'Hardened',
|
||||||
|
id: 'hardened',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'Shaped',
|
||||||
|
id: 'shaped',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'Size',
|
||||||
|
id: 'size',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _length() {
|
||||||
|
let height = 10;
|
||||||
|
if (this.data.raise) {
|
||||||
|
height *= 2;
|
||||||
|
}
|
||||||
|
if (this.data.size) {
|
||||||
|
height *= 2;
|
||||||
|
}
|
||||||
|
return `${height}" (${height * 2} yards)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _height() {
|
||||||
|
return `${this.data.size ? '2" (4' : '1" (2'} yards)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _hardness() {
|
||||||
|
return (this.data.raise ? 12 : 10) + (this.data.hardened ? 2 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get maintEffectButtons() {
|
||||||
|
const buttons = super.primaryEffectButtons;
|
||||||
|
if (this.data.damage != 'none') {
|
||||||
|
const damage = this.data.damage === 'deadly' ? '2d6' : '2d4';
|
||||||
|
buttons.push({
|
||||||
|
label: `Damage (${damage})`,
|
||||||
|
type: 'damage',
|
||||||
|
formula: damage + 'x',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let text = super.description;
|
||||||
|
text += `<p>A barrier ${this._height} tall and ${this._length} long, of hardness ${this._hardness}. `;
|
||||||
|
if (this.data.damage === 'deadly') {
|
||||||
|
text += 'It does 2d6 damage to anyone who contacts it. ';
|
||||||
|
} else if (this.data.damage != 'none') {
|
||||||
|
text += 'It does 2d4 damage to anyone who contacts it. ';
|
||||||
|
}
|
||||||
|
if (this.data.shaped) {
|
||||||
|
text += 'It was shaped into a circle, square, or rectangle. ';
|
||||||
|
}
|
||||||
|
text += '</p>';
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
1076
src/module/powers/basePowers.js
Normal file
1076
src/module/powers/basePowers.js
Normal file
@ -0,0 +1,1076 @@
|
|||||||
|
import { moduleName, moduleHelpers, log, settingKeys } from '../globals.js';
|
||||||
|
import { firstOwner, addActiveEffectsToToken } from '../helpers.js';
|
||||||
|
import { templates } from '../preloadTemplates.js';
|
||||||
|
|
||||||
|
const MAINTAIN_ICONS = [
|
||||||
|
'icons/magic/symbols/rune-sigil-black-pink.webp',
|
||||||
|
'icons/magic/symbols/rune-sigil-green-purple.webp',
|
||||||
|
'icons/magic/symbols/rune-sigil-hook-white-red.webp',
|
||||||
|
'icons/magic/symbols/rune-sigil-red-orange.webp',
|
||||||
|
'icons/magic/symbols/rune-sigil-rough-white-teal.webp',
|
||||||
|
'icons/magic/symbols/rune-sigil-white-pink.webp',
|
||||||
|
'icons/magic/symbols/runes-star-blue.webp',
|
||||||
|
'icons/magic/symbols/runes-star-magenta.webp',
|
||||||
|
'icons/magic/symbols/runes-star-orange-purple.webp',
|
||||||
|
'icons/magic/symbols/runes-star-orange.webp',
|
||||||
|
'icons/magic/symbols/runes-star-pentagon-blue.webp',
|
||||||
|
'icons/magic/symbols/runes-star-pentagon-magenta.webp',
|
||||||
|
'icons/magic/symbols/runes-star-pentagon-orange-purple.webp',
|
||||||
|
'icons/magic/symbols/runes-star-pentagon-orange.webp',
|
||||||
|
'icons/magic/symbols/runes-triangle-blue.webp',
|
||||||
|
'icons/magic/symbols/runes-triangle-magenta.webp',
|
||||||
|
'icons/magic/symbols/runes-triangle-orange-purple.webp',
|
||||||
|
'icons/magic/symbols/runes-triangle-orange.webp',
|
||||||
|
'icons/magic/symbols/triangle-glow-purple.webp',
|
||||||
|
'icons/magic/symbols/triangle-glowing-green.webp',
|
||||||
|
];
|
||||||
|
|
||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
function _hashCode(str) {
|
||||||
|
let hash = 0;
|
||||||
|
if (str.length === 0) {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const c = str.charCodeAt(i);
|
||||||
|
hash = (hash << 5) - hash + c;
|
||||||
|
hash |= 0;
|
||||||
|
}
|
||||||
|
return Math.abs(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PowerFormApplication extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(powerEffect) {
|
||||||
|
const name = powerEffect.name.replaceAll(/[^a-zA-Z]/g, '');
|
||||||
|
const id = `${PowerFormApplication.DEFAULT_OPTIONS.id}${name}`;
|
||||||
|
super({ id });
|
||||||
|
this.powerEffect = powerEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: 'mbSwadePowerEffectsApplicationV2',
|
||||||
|
form: {
|
||||||
|
handler: PowerFormApplication.#onSubmit,
|
||||||
|
closeOnSubmit: true,
|
||||||
|
},
|
||||||
|
tag: 'form',
|
||||||
|
position: {
|
||||||
|
width: 600,
|
||||||
|
height: 'auto',
|
||||||
|
},
|
||||||
|
classes: ['mbSwade', 'mbSwadeForm', 'mbSwadePowerEffectsForm'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-hand-sparkles',
|
||||||
|
title: 'Apply Effect',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: {
|
||||||
|
template: templates['dialogHeader.html'],
|
||||||
|
classes: ['mbSwade', 'mbSwadeDialogHeader', 'mbSwadePowerEffectsHeader'],
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
template: templates['powerDialog.html'],
|
||||||
|
classes: ['mbSwade', 'mbSwadePowerEffectsBody', 'scrollable'],
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
template: 'templates/generic/form-footer.hbs',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static sortMods(a, b) {
|
||||||
|
if (a.isGlobal !== b.isGlobal) {
|
||||||
|
return a.isGlobal ? -1 : 1;
|
||||||
|
}
|
||||||
|
if ((a.sortOrder ?? 0) !== (b.sortOrder ?? 0)) {
|
||||||
|
return (a.sortOrder ?? 0) < (b.sortOrder ?? 0) ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (a.type !== b.type) {
|
||||||
|
return a.type === 'checkbox' ? -1 : 1;
|
||||||
|
}
|
||||||
|
return a.name === b.name ? 0 : a.name < b.name ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
await this.powerEffect.init();
|
||||||
|
let modifiers = foundry.utils.deepClone(this.powerEffect.modifiers);
|
||||||
|
modifiers.sort(PowerFormApplication.sortMods);
|
||||||
|
for (const modifier of modifiers) {
|
||||||
|
modifier.isCheckbox = modifier.type === 'checkbox';
|
||||||
|
modifier.isSelect = modifier.type === 'select';
|
||||||
|
modifier.isRadio = modifier.type === 'radio';
|
||||||
|
modifier.isNumber = modifier.type === 'number';
|
||||||
|
modifier.isText = modifier.type === 'text';
|
||||||
|
if (modifier.isNumber) {
|
||||||
|
modifier.step = modifier?.step ?? 1;
|
||||||
|
}
|
||||||
|
if (modifier.isSelect || modifier.isRadio) {
|
||||||
|
for (const choice in modifier.choices) {
|
||||||
|
let val = '';
|
||||||
|
if (modifier.values[choice] !== 0) {
|
||||||
|
val = ` (${modifier.values[choice] > 0 ? '+' : ''}${modifier.values[choice]})`;
|
||||||
|
}
|
||||||
|
modifier.choices[choice] = `${modifier.choices[choice]}${val}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modifier.isRadio) {
|
||||||
|
for (const choice in modifier.choices) {
|
||||||
|
let val = modifier.choices[choice];
|
||||||
|
modifier.choices[choice] = { text: val, checked: choice == modifier.default };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
name: this.powerEffect.name,
|
||||||
|
formId: foundry.utils.randomID(),
|
||||||
|
headerTitle: `${this.powerEffect.name} Effect`,
|
||||||
|
headerSubtitle: `Apply the effects from ${this.powerEffect.name}`,
|
||||||
|
icon: this.powerEffect.icon,
|
||||||
|
basePowerPoints: this.powerEffect.basePowerPoints,
|
||||||
|
modifiers,
|
||||||
|
recipients: {
|
||||||
|
cost: 0,
|
||||||
|
number: 0,
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
extraDescription: this.powerEffect.extraDescription,
|
||||||
|
targets: [],
|
||||||
|
buttons: this.powerEffect.menuButtons,
|
||||||
|
};
|
||||||
|
for (let button of data.buttons) {
|
||||||
|
button.action = button.value;
|
||||||
|
button.type = button.value === 'cancel' ? 'cancel' : 'submit';
|
||||||
|
}
|
||||||
|
if (this.powerEffect.isTargeted) {
|
||||||
|
if (this.powerEffect.oneTarget) {
|
||||||
|
data.targets = [this.powerEffect.targets?.[0]?.name ?? '<em>No Target Selected!</em>'];
|
||||||
|
} else {
|
||||||
|
data.targets = this.powerEffect.targets.map((t) => t.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.powerEffect.hasAdditionalRecipients && this.powerEffect.targets.length > 1) {
|
||||||
|
data.recipients.cost = this.powerEffect.additionalRecipientCost;
|
||||||
|
data.recipients.count = this.powerEffect.additionalRecipientCount;
|
||||||
|
data.recipients.total = data.recipients.cost * data.recipients.count;
|
||||||
|
data.recipients.epic = this.powerEffect.additionalRecipientsIsEpic;
|
||||||
|
data.recipients.text = this.powerEffect.additionalRecipientText;
|
||||||
|
}
|
||||||
|
log('DATA', data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onSubmit(event, form, formData) {
|
||||||
|
log(this.formData);
|
||||||
|
formData.object.submit = event?.submitter?.dataset?.action ?? 'cancel';
|
||||||
|
if (formData.object.submit !== 'cancel') {
|
||||||
|
this.powerEffect.formData = formData.object;
|
||||||
|
this.powerEffect.applyEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PowerEffect {
|
||||||
|
constructor(token, targets, item) {
|
||||||
|
this.source = token;
|
||||||
|
this.targets = targets;
|
||||||
|
this.item = item;
|
||||||
|
this.data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
log('Power Effect', this.name, 'Init');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getStatus(label, name, favorite = true) {
|
||||||
|
const effect = foundry.utils.deepClone(CONFIG.statusEffects.find((se) => se.label === label || se.name === label));
|
||||||
|
effect.name = 'name' in effect ? effect.name : effect.label;
|
||||||
|
effect.duration = {};
|
||||||
|
if (!('flags' in effect)) {
|
||||||
|
effect.flags = {};
|
||||||
|
}
|
||||||
|
effect.flags.swade = {};
|
||||||
|
if (favorite) {
|
||||||
|
effect.flags.swade.favorite = true;
|
||||||
|
}
|
||||||
|
effect.statuses ??= [];
|
||||||
|
effect.statuses.push(effect.id);
|
||||||
|
return effect;
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffectDocument(icon, name, changes = null) {
|
||||||
|
if (changes === null) {
|
||||||
|
changes = [];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
changes,
|
||||||
|
description: `<p>From <strong>${this.source.name}</strong> casting <em>${this.name}</em></p>`,
|
||||||
|
duration: {},
|
||||||
|
system: {
|
||||||
|
loseTurnOnHold: false,
|
||||||
|
expiration: null,
|
||||||
|
},
|
||||||
|
flags: {
|
||||||
|
[moduleName]: {
|
||||||
|
powerEffect: true,
|
||||||
|
},
|
||||||
|
swade: {
|
||||||
|
loseTurnOnHold: false,
|
||||||
|
expiration: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyActiveEffects(token, effectDocuments) {
|
||||||
|
const owner = firstOwner(token);
|
||||||
|
await moduleHelpers.socket.executeAsUser(
|
||||||
|
addActiveEffectsToToken,
|
||||||
|
owner.id,
|
||||||
|
token?.scene?.id ?? token.parent.id,
|
||||||
|
token.id,
|
||||||
|
effectDocuments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return 'Unknown Power';
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get extraDescription() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/symbols/question-stone-yellow.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDamaging() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAdditionalRecipients() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientsIsEpic() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientText() {
|
||||||
|
return 'Additional Recipients';
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientCount() {
|
||||||
|
if (!this.hasAdditionalRecipients) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.max(0, this.targets.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientCost() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get oneTarget() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasRange() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRaisable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAoe() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = [];
|
||||||
|
mods.push({
|
||||||
|
name: 'Adaptable Caster',
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
id: 'adaptable',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Fatigue',
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
id: 'fatigue',
|
||||||
|
value: 2,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Glow/Shroud',
|
||||||
|
id: 'glowshroud',
|
||||||
|
type: 'radio',
|
||||||
|
isGlobal: true,
|
||||||
|
default: 'none',
|
||||||
|
choices: { none: 'None', glow: 'Glow', shroud: 'Shroud' },
|
||||||
|
values: { none: 0, glow: 1, shroud: 1 },
|
||||||
|
effects: {
|
||||||
|
none: null,
|
||||||
|
glow: {
|
||||||
|
name: 'Glow',
|
||||||
|
icon: 'icons/magic/light/orb-shadow-blue.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: '@Skill{Stealth}[system.die.modifier]',
|
||||||
|
value: -2,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
shroud: {
|
||||||
|
name: 'Shroud',
|
||||||
|
icon: 'icons/magic/perception/shadow-stealth-eyes-purple.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: '@Skill{Stealth}[system.die.modifier]',
|
||||||
|
value: 1,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
epic: false,
|
||||||
|
});
|
||||||
|
if (this.isDamaging) {
|
||||||
|
mods.push({
|
||||||
|
name: 'Armor Piercing',
|
||||||
|
id: 'ap',
|
||||||
|
type: 'select',
|
||||||
|
default: 'none',
|
||||||
|
choices: { none: 'None', 2: 'AP 2', 4: 'AP 4', 6: 'AP 6' },
|
||||||
|
values: { none: 0, 2: 1, 4: 2, 6: 3 },
|
||||||
|
effects: { none: null, 2: null, 4: null, 6: null },
|
||||||
|
epic: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Lingering Damage',
|
||||||
|
id: 'lingeringdamage',
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
value: 2,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Heavy Weapon',
|
||||||
|
id: 'heavyweapon',
|
||||||
|
value: 2,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
mods.push({
|
||||||
|
name: 'Hinder/Hurry',
|
||||||
|
id: 'hinderhurry',
|
||||||
|
type: 'radio',
|
||||||
|
default: 'none',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
choices: { none: 'None', hinder: 'Hinder', hurry: 'Hurry' },
|
||||||
|
values: { none: 0, hinder: 1, hurry: 1 },
|
||||||
|
isGlobal: true,
|
||||||
|
effects: {
|
||||||
|
none: null,
|
||||||
|
hinder: {
|
||||||
|
name: 'Hinder',
|
||||||
|
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'system.pace',
|
||||||
|
value: -2,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
hurry: {
|
||||||
|
name: 'Hurry',
|
||||||
|
icon: 'icons/skills/movement/feet-winged-sandals-tan.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'system.pace',
|
||||||
|
value: 2,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (this.hasAoe) {
|
||||||
|
mods.push({
|
||||||
|
type: 'checkbox',
|
||||||
|
default: false,
|
||||||
|
name: 'Selective',
|
||||||
|
id: 'selective',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
isGlobal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.hasRange) {
|
||||||
|
mods.push({
|
||||||
|
type: 'select',
|
||||||
|
default: 0,
|
||||||
|
name: 'Range',
|
||||||
|
id: 'range',
|
||||||
|
choices: {
|
||||||
|
normal: 'Normal Range',
|
||||||
|
x2: 'Range ×2',
|
||||||
|
x3: 'Range ×3',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
normal: 0,
|
||||||
|
x2: 1,
|
||||||
|
x3: 2,
|
||||||
|
},
|
||||||
|
isGlobal: true,
|
||||||
|
effects: { normal: null, x2: null, x3: null },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get menuButtons() {
|
||||||
|
const data = [{ label: 'Apply', value: 'apply' }];
|
||||||
|
if (this.isRaisable) {
|
||||||
|
data.push({ label: 'Apply with Raise', value: 'raise' });
|
||||||
|
}
|
||||||
|
data.push({ label: 'Cancel', value: 'cancel' });
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
new PowerFormApplication(this).render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyEffect() {
|
||||||
|
await this.parseValues();
|
||||||
|
await this.apply();
|
||||||
|
await this.chatMessage();
|
||||||
|
await this.sideEffects();
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseValues() {
|
||||||
|
this.data.raise = this.formData.submit === 'raise';
|
||||||
|
this.data.button = this.formData.submit;
|
||||||
|
this.data.maintId = foundry.utils.randomID();
|
||||||
|
for (const mod of this.modifiers) {
|
||||||
|
this.data[mod.id] = this.formData[mod.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enhanceSecondaryEffect(maintId, doc) {
|
||||||
|
doc.statuses = doc.statuses ?? [];
|
||||||
|
doc.statuses.push('powerEffect');
|
||||||
|
if (this.duration === 0 && !this.usePrimaryEffect) {
|
||||||
|
// set secondary effects of instant spells to expire on victim's next
|
||||||
|
// turn
|
||||||
|
doc.duration.rounds = 1;
|
||||||
|
doc.flags.swade.expiration = CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.EndOfTurnAuto;
|
||||||
|
doc.system.expiration = doc.flags.swade.expiration;
|
||||||
|
} else {
|
||||||
|
doc.flags[moduleName].maintId = maintId;
|
||||||
|
if (moduleHelpers.useVAE) {
|
||||||
|
doc.flags['visual-active-effects'] = {
|
||||||
|
data: {
|
||||||
|
inclusion: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
doc.duration.seconds = 594;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSecondaryEffects(maintId) {
|
||||||
|
const docs = [];
|
||||||
|
for (const mod of this.modifiers) {
|
||||||
|
const modValue = this.data[mod.id];
|
||||||
|
if (modValue && (mod?.effect || (mod?.effects?.[modValue] ?? false))) {
|
||||||
|
const icon = 'effects' in mod ? mod.effects[modValue].icon : mod.icon;
|
||||||
|
const name = 'effects' in mod ? mod.effects[modValue].name : mod.name;
|
||||||
|
const changes = 'effects' in mod ? mod.effects[modValue].changes : mod.changes;
|
||||||
|
const doc = this.enhanceSecondaryEffect(maintId, this.createEffectDocument(icon, name, changes));
|
||||||
|
const desc = 'effects' in mod ? mod.effects?.[modValue]?.description : mod.description;
|
||||||
|
if (desc) {
|
||||||
|
doc.description += desc;
|
||||||
|
}
|
||||||
|
docs.push(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimaryEffectChanges() {
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'flags.swade-mb-helpers.powerAffected',
|
||||||
|
value: 1,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaintainEffectChanges() {
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'flags.swade-mb-helpers.powerMaintained',
|
||||||
|
value: 1,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
get primaryEffectButtons() {
|
||||||
|
// button objects should have a label and a type.
|
||||||
|
// type should have one of the following, with the associated additional
|
||||||
|
// fields:
|
||||||
|
// roll:
|
||||||
|
// formula: dice formula eg '3d6 + 3'
|
||||||
|
// flavor: flavor text (optional)
|
||||||
|
// trait:
|
||||||
|
// rollType: 'attribute' or 'skill
|
||||||
|
// rollDesc: name or swid of the attribute or skill
|
||||||
|
// flavor: flavor text (optional)
|
||||||
|
// mods: list of mods { label, value, ignore }
|
||||||
|
// damage:
|
||||||
|
// formula: dice formula for example '1d4x[Blades]'
|
||||||
|
// ap: optional, a positive integer or 0, armor piercing
|
||||||
|
// flavor: flavor text (optional)
|
||||||
|
// callback:
|
||||||
|
// callback: the function callback to run, takes a token as an argument
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get maintEffectButtons() {
|
||||||
|
// see the comment for primaryEffectButtons
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePrimaryEffect() {
|
||||||
|
return this.createEffectDocument(this.icon, this.effectName, this.getPrimaryEffectChanges());
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPrimaryEffect(maintId) {
|
||||||
|
const doc = this.basePrimaryEffect;
|
||||||
|
if (moduleHelpers.useVAE) {
|
||||||
|
doc.flags['visual-active-effects'] = {
|
||||||
|
data: {
|
||||||
|
inclusion: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
doc.description += this.description;
|
||||||
|
doc.statuses = doc.statuses ?? [];
|
||||||
|
doc.statuses.push('powerEffect');
|
||||||
|
doc.flags[moduleName].maintId = maintId;
|
||||||
|
const effectButtons = this.primaryEffectButtons;
|
||||||
|
if (effectButtons.length > 0) {
|
||||||
|
doc.flags[moduleName].buttons = effectButtons;
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createMaintainEffect(maintId) {
|
||||||
|
let icon = MAINTAIN_ICONS[_hashCode(this.name) % MAINTAIN_ICONS.length];
|
||||||
|
if (!this.usePrimaryEffect) {
|
||||||
|
icon = this.icon;
|
||||||
|
}
|
||||||
|
const doc = this.createEffectDocument(icon, `Maintaining ${this.effectName}`, this.getMaintainEffectChanges());
|
||||||
|
doc.duration.rounds = this.duration;
|
||||||
|
doc.description += this.description;
|
||||||
|
doc.flags.swade.expiration = CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.EndOfTurnPrompt;
|
||||||
|
doc.flags.swade.loseTurnOnHold = true;
|
||||||
|
doc.system.expiration = doc.flags.swade.expiration;
|
||||||
|
doc.system.loseTurnOnHold = true;
|
||||||
|
doc.flags[moduleName].maintainingId = maintId;
|
||||||
|
if (this.isTargeted) {
|
||||||
|
doc.flags[moduleName].targetIds = this.targets.map((t) => t.id);
|
||||||
|
} else {
|
||||||
|
doc.flags[moduleName].targetIds = [this.source.id];
|
||||||
|
}
|
||||||
|
doc.statuses = doc.statuses ?? [];
|
||||||
|
doc.statuses.push('powerMaintainEffect');
|
||||||
|
const effectButtons = this.maintEffectButtons;
|
||||||
|
if (effectButtons.length > 0) {
|
||||||
|
doc.flags[moduleName].buttons = effectButtons;
|
||||||
|
}
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
async secondaryDocsForTarget(docs, target) {
|
||||||
|
return foundry.utils.deepClone(docs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async primaryDocForTarget(doc, target) {
|
||||||
|
const newDoc = foundry.utils.deepClone(doc);
|
||||||
|
newDoc.flags[moduleName].maintainingId = doc.flags[moduleName].maintId;
|
||||||
|
newDoc.flags[moduleName].targetIds = [target.id];
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async apply() {
|
||||||
|
const maintId = this.data.maintId;
|
||||||
|
const secondaryDocs = await this.createSecondaryEffects(maintId);
|
||||||
|
const primaryDoc = await this.createPrimaryEffect(maintId);
|
||||||
|
const maintainDoc = await this.createMaintainEffect(maintId);
|
||||||
|
if (this.isTargeted) {
|
||||||
|
for (const target of this.targets) {
|
||||||
|
const targetDocs = await this.secondaryDocsForTarget(secondaryDocs, target);
|
||||||
|
if (this.duration > 0 || this.usePrimaryEffect) {
|
||||||
|
targetDocs.push(await this.primaryDocForTarget(primaryDoc, target));
|
||||||
|
}
|
||||||
|
if (targetDocs.length > 0) {
|
||||||
|
await this.applyActiveEffects(target, targetDocs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const targetDocs = await this.secondaryDocsForTarget(secondaryDocs, this.source);
|
||||||
|
if (targetDocs.length > 0) {
|
||||||
|
await this.applyActiveEffects(this.source, targetDocs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.duration > 0) {
|
||||||
|
await this.applyActiveEffects(this.source, [maintainDoc]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sideEffects() {
|
||||||
|
if (this.data.fatigue && this.isTargeted) {
|
||||||
|
for (const target of this.targets) {
|
||||||
|
const actor = target.actor;
|
||||||
|
const update = {
|
||||||
|
system: {
|
||||||
|
fatigue: {
|
||||||
|
value: actor.system.fatigue.value + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (actor.system.fatigue.value < actor.system.fatigue.max) {
|
||||||
|
await actor.update(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get powerPoints() {
|
||||||
|
let total = this.basePowerPoints;
|
||||||
|
for (const mod of this.modifiers) {
|
||||||
|
const modValue = this.data[mod.id];
|
||||||
|
if (modValue) {
|
||||||
|
if ('values' in mod) {
|
||||||
|
total += mod.values[modValue];
|
||||||
|
} else {
|
||||||
|
total += mod.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total += this.additionalRecipientCost * this.additionalRecipientCount;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chatMessageEffects() {
|
||||||
|
const list = [];
|
||||||
|
if (this.hasAdditionalRecipients && this.targets.length > 1) {
|
||||||
|
list.push(`${this.targets.length - 1} Additional Recipients`);
|
||||||
|
}
|
||||||
|
if (this.data.adaptable) {
|
||||||
|
list.push('Different Trapping (Adaptable Caster)');
|
||||||
|
}
|
||||||
|
if (this.data.fatigue) {
|
||||||
|
list.push('Fatigue (applied to targets)');
|
||||||
|
}
|
||||||
|
if (this.data.heavyweapon) {
|
||||||
|
list.push('Heavy Weapon');
|
||||||
|
}
|
||||||
|
if (this.data.lingeringdamage) {
|
||||||
|
list.push('Lingering Damage');
|
||||||
|
}
|
||||||
|
if (this.data.selective) {
|
||||||
|
list.push('Selective');
|
||||||
|
}
|
||||||
|
if (this.isDamaging && this.data.ap > 0) {
|
||||||
|
list.push(`AP ${this.data.ap}`);
|
||||||
|
}
|
||||||
|
if (this.data.range ?? 'none' != 'none') {
|
||||||
|
list.push(`Range ${this.data.range}`);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chatMessageText() {
|
||||||
|
let text = `<p>Cast ${this.name}`;
|
||||||
|
if (this.isTargeted && this.targets.length > 0) {
|
||||||
|
text += ` on ${this.targets.map((t) => t.name).join(', ')}`;
|
||||||
|
}
|
||||||
|
text += '</p>';
|
||||||
|
const desc = this.description;
|
||||||
|
if (desc) {
|
||||||
|
text += `<details open><summary>Description</summary>${desc}</details>`;
|
||||||
|
}
|
||||||
|
const effects = this.chatMessageEffects;
|
||||||
|
if (effects.length > 0) {
|
||||||
|
text += '<details><summary>Other Effects:</summary><ul><li>' + effects.join('</li><li>') + '</li></ul></details>';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
async chatMessage() {
|
||||||
|
return ChatMessage.create(
|
||||||
|
{
|
||||||
|
flavor: `Calculated cost: ${this.powerPoints} pp`,
|
||||||
|
speaker: ChatMessage.getSpeaker(this.source.actor),
|
||||||
|
content: this.chatMessageText,
|
||||||
|
whisper: ChatMessage.getWhisperRecipients('GM', game.user.name),
|
||||||
|
},
|
||||||
|
{ chatBubble: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActorFolderEffect extends PowerEffect {
|
||||||
|
async init() {
|
||||||
|
await super.init();
|
||||||
|
const extraFields = ['system.stats.size', 'flags.swade-mb-helpers.summonData'];
|
||||||
|
this.packActors = await ActorFolderEffect.actorFolderPack.getIndex({ fields: extraFields });
|
||||||
|
this.data.actors = this.prepActors();
|
||||||
|
}
|
||||||
|
|
||||||
|
get actorFolderBase() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get actorFolderPack() {
|
||||||
|
const packId = moduleHelpers.getSetting(settingKeys.powerActorsCompendium);
|
||||||
|
const pack = game.packs.get(packId);
|
||||||
|
if (!pack) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return game.user.isGM || pack.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER)
|
||||||
|
? pack
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
get actorFolder() {
|
||||||
|
return `${this.actorFolderBase}/${this.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPackFolderByPath(path) {
|
||||||
|
const names = path.split('/');
|
||||||
|
if (names[0] === '') {
|
||||||
|
names.shift();
|
||||||
|
}
|
||||||
|
let name = names.shift();
|
||||||
|
if (!ActorFolderEffect.actorFolderPack) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let folder = ActorFolderEffect.actorFolderPack.folders
|
||||||
|
.filter((f) => f.type === 'Actor' && !f.folder)
|
||||||
|
.find((f) => f.name === name);
|
||||||
|
if (!folder) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
while (names.length > 0) {
|
||||||
|
name = names.shift();
|
||||||
|
folder = folder.children.find((c) => c.folder.name === name);
|
||||||
|
if (!folder) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
folder = folder.folder;
|
||||||
|
}
|
||||||
|
return folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPackActorsInFolder(inFolder) {
|
||||||
|
const prefixStack = [''];
|
||||||
|
const actors = {};
|
||||||
|
const folderStack = [inFolder];
|
||||||
|
while (folderStack.length > 0) {
|
||||||
|
const prefix = prefixStack.shift();
|
||||||
|
const folder = folderStack.shift();
|
||||||
|
for (const actor of folder.contents) {
|
||||||
|
actors[`${prefix}${actor.name}`] = actor;
|
||||||
|
}
|
||||||
|
for (const child of folder.children) {
|
||||||
|
const newPrefix = `${prefix}${child.folder.name} | `;
|
||||||
|
prefixStack.push(newPrefix);
|
||||||
|
folderStack.push(child.folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actors;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepFolders() {
|
||||||
|
const folders = [];
|
||||||
|
const folderNames = [
|
||||||
|
this.actorFolder,
|
||||||
|
`${this.actorFolder} - Default`,
|
||||||
|
`${this.actorFolder}/Default`,
|
||||||
|
`${this.actorFolder} - ${this.source.name}`,
|
||||||
|
`${this.actorFolder} - ${this.source.actor.name}`,
|
||||||
|
`${this.actorFolder}/${this.source.name}`,
|
||||||
|
`${this.actorFolder}/${this.source.actor.name}`,
|
||||||
|
];
|
||||||
|
for (const folderName of folderNames) {
|
||||||
|
const folder = ActorFolderEffect.getPackFolderByPath(folderName);
|
||||||
|
if (folder) {
|
||||||
|
log(`Found actor folder ${folderName}`);
|
||||||
|
folders.push(folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (folders.length > 1) {
|
||||||
|
folders.shift();
|
||||||
|
}
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepActors() {
|
||||||
|
const folders = this.prepFolders();
|
||||||
|
const actors = {};
|
||||||
|
for (const folder of folders) {
|
||||||
|
const folderActors = ActorFolderEffect.getPackActorsInFolder(folder);
|
||||||
|
for (const key in folderActors) {
|
||||||
|
actors[key] = folderActors[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
actorValue(actor) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActors() {
|
||||||
|
const choices = {};
|
||||||
|
const effects = {};
|
||||||
|
const values = {};
|
||||||
|
Object.keys(this.data.actors)
|
||||||
|
.filter((k) => !k.includes('_template'))
|
||||||
|
.sort()
|
||||||
|
.forEach((key) => {
|
||||||
|
const id = this.data.actors[key].uuid;
|
||||||
|
choices[id] = key;
|
||||||
|
effects[id] = null;
|
||||||
|
values[id] = this.actorValue(this.data.actors[key]);
|
||||||
|
});
|
||||||
|
return { choices, effects, values };
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const { choices, effects, values } = this.getActors();
|
||||||
|
return [
|
||||||
|
...super.modifiers,
|
||||||
|
{
|
||||||
|
name: 'Select Creature',
|
||||||
|
id: 'actorId',
|
||||||
|
type: 'select',
|
||||||
|
choices,
|
||||||
|
effects,
|
||||||
|
values,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get spawnUpdates() {
|
||||||
|
const updates = {
|
||||||
|
actor: {},
|
||||||
|
token: {
|
||||||
|
actorLink: false,
|
||||||
|
},
|
||||||
|
embedded: {
|
||||||
|
ActiveEffect: {},
|
||||||
|
Item: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
#documentFinder(documentType, oldDoc, newDoc) {
|
||||||
|
if (documentType === 'Item') {
|
||||||
|
return oldDoc.name.toLowerCase() === newDoc.name.toLowerCase() && oldDoc.type === newDoc.type;
|
||||||
|
}
|
||||||
|
return oldDoc.name.toLowerCase() === newDoc.name.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateEmbedded(actor, newDocs) {
|
||||||
|
const adds = {};
|
||||||
|
const updates = {};
|
||||||
|
for (const documentType of Object.keys(newDocs ?? {})) {
|
||||||
|
const collection = actor.getEmbeddedCollection(documentType);
|
||||||
|
adds[documentType] = [];
|
||||||
|
updates[documentType] = [];
|
||||||
|
log('docType', documentType);
|
||||||
|
for (const newDocKey in newDocs[documentType]) {
|
||||||
|
log('newDocKey', newDocKey);
|
||||||
|
const newDoc = newDocs[documentType][newDocKey].toObject();
|
||||||
|
const oldDoc = collection.find((doc) => this.#documentFinder(documentType, doc, newDoc));
|
||||||
|
if (newDoc.type === 'power' && newDoc?.system?.choiceSets?.length > 0) {
|
||||||
|
newDoc.system.choiceSets = [];
|
||||||
|
}
|
||||||
|
if (oldDoc) {
|
||||||
|
const _id = oldDoc.id;
|
||||||
|
updates[documentType].push({ ...newDoc, _id });
|
||||||
|
} else {
|
||||||
|
adds[documentType].push(newDoc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updateOpts = {};
|
||||||
|
updateOpts.mbItemCreationSource = moduleName;
|
||||||
|
if (documentType === 'Item') {
|
||||||
|
updateOpts.renderSheet = false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (adds[documentType].length > 0) {
|
||||||
|
actor.createEmbeddedDocuments(documentType, adds[documentType], updateOpts);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('ERROR', e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (updates[documentType].length > 0) {
|
||||||
|
actor.updateEmbeddedDocuments(documentType, updates[documentType], updateOpts);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log('ERROR', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseValues() {
|
||||||
|
await super.parseValues();
|
||||||
|
this.data.maintId = foundry.utils.randomID();
|
||||||
|
this.targetActor = await game.tcal.importTransientActor(this.data.actorId, { preferExisting: true });
|
||||||
|
this.targetTokenDoc = await this.targetActor.getTokenDocument();
|
||||||
|
const perm = CONST?.DOCUMENT_PERMISSION_LEVELS?.OWNER ?? CONST?.DOCUMENT_OWNERSHIP_LEVELS?.OWNER;
|
||||||
|
const sourceUpdates = {
|
||||||
|
delta: {
|
||||||
|
ownership: {
|
||||||
|
[game.user.id]: perm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.targetTokenDoc.updateSource(sourceUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawn() {
|
||||||
|
this.targetTokenDoc.updateSource({
|
||||||
|
x: this.source.x,
|
||||||
|
y: this.source.y,
|
||||||
|
elevation: this.source.elevation,
|
||||||
|
});
|
||||||
|
return this.source.scene.createEmbeddedDocuments('Token', [this.targetTokenDoc]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async apply() {
|
||||||
|
this.data.spawned = await this.spawn();
|
||||||
|
const updates = this.spawnUpdates;
|
||||||
|
const secondaryDocs = await this.createSecondaryEffects(this.data.maintId);
|
||||||
|
const primaryDoc = await this.createPrimaryEffect(this.data.maintId);
|
||||||
|
const promises = [];
|
||||||
|
for (const token of this.data.spawned) {
|
||||||
|
if (updates?.token) {
|
||||||
|
promises.push(token.update(updates.token));
|
||||||
|
}
|
||||||
|
if (updates?.actor) {
|
||||||
|
promises.push(token.actor.update(updates.actor));
|
||||||
|
}
|
||||||
|
if (updates?.embedded) {
|
||||||
|
promises.push(this.updateEmbedded(token.actor, updates.embedded));
|
||||||
|
}
|
||||||
|
const activeEffects = await this.secondaryDocsForTarget(secondaryDocs, token);
|
||||||
|
activeEffects.push(await this.primaryDocForTarget(primaryDoc, token));
|
||||||
|
promises.push(this.applyActiveEffects(token, activeEffects));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPrimaryEffect(maintId) {
|
||||||
|
const doc = await super.createPrimaryEffect(maintId);
|
||||||
|
doc.flags[moduleName].spawnedTempToken = true;
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sideEffects() {
|
||||||
|
if (this.data.fatigue) {
|
||||||
|
for (const target of this.data.spawned) {
|
||||||
|
const actor = target.actor;
|
||||||
|
const update = {
|
||||||
|
system: {
|
||||||
|
fatigue: {
|
||||||
|
value: actor.system.fatigue.value + 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (actor.system.fatigue.value < actor.system.fatigue.max) {
|
||||||
|
await actor.update(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function embeddedHelperHook(item, data, options) {
|
||||||
|
if (options?.mbItemCreationSource === moduleName) {
|
||||||
|
options.renderSheet = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/module/powers/beastFriend.js
Normal file
90
src/module/powers/beastFriend.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BeastFriendEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Beast Friend';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return (this.data.duration ? 30 : 10) * 6 * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/nature/wolf-paw-glow-large-green.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
const ppDefault = Math.max(
|
||||||
|
this.targets.map((t) => Math.max(t.actor.system.stats.size, 1)).reduce((a, b) => a + b, 0),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
mods.push(
|
||||||
|
{
|
||||||
|
name: 'Bestiarium',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 2,
|
||||||
|
id: 'bestiarium',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Duration',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 1,
|
||||||
|
id: 'duration',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mind Rider',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 1,
|
||||||
|
id: 'mindrider',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Base power points',
|
||||||
|
type: 'number',
|
||||||
|
value: 0,
|
||||||
|
default: ppDefault,
|
||||||
|
id: 'basePP',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return (
|
||||||
|
this.data.basePP ??
|
||||||
|
Math.max(
|
||||||
|
this.targets.map((t) => Math.max(t.actor.system.stats.size, 1)).reduce((a, b) => a + b, 0),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let text = super.description;
|
||||||
|
if (this.data.raise) {
|
||||||
|
text += '<p>Creatures will overcome instincts to follow orders.';
|
||||||
|
} else {
|
||||||
|
text += '<p>Creatures obey simple commands, subject to their insticts.';
|
||||||
|
}
|
||||||
|
if (this.data.bestiarium) {
|
||||||
|
text += ' The caster may even effect magical beasts.';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/module/powers/blast.js
Normal file
70
src/module/powers/blast.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BlastEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Blast';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/fire/explosion-fireball-large-red-orange.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDamaging() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAoe() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push(
|
||||||
|
{
|
||||||
|
name: 'Area of Effect',
|
||||||
|
id: 'aoe',
|
||||||
|
type: 'select',
|
||||||
|
default: 'mbt',
|
||||||
|
choices: {
|
||||||
|
sbt: 'Small Blast Template',
|
||||||
|
mbt: 'Medium Blast Template',
|
||||||
|
lbt: 'Large Blast Template',
|
||||||
|
},
|
||||||
|
effects: { sbt: null, mbt: null, lbt: null },
|
||||||
|
values: { sbt: 0, mbt: 0, lbt: 1 },
|
||||||
|
epic: false,
|
||||||
|
},
|
||||||
|
{ name: 'Damage', value: 2, id: 'damage', epic: false, effect: false },
|
||||||
|
{
|
||||||
|
name: 'Greater Blast',
|
||||||
|
value: 4,
|
||||||
|
id: 'greater',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
const dmgDie = (this.data.greater ? 4 : this.data.damage ? 3 : 2) + (this.data.raise ? 1 : 0);
|
||||||
|
const size = this.data.aoe.toUpperCase();
|
||||||
|
return super.description + `<p>The blast covers a ${size} and does ${dmgDie}d6 damage</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
126
src/module/powers/blind.js
Normal file
126
src/module/powers/blind.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { moduleName } from '../globals.js';
|
||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BlindEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Blind';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/skills/wounds/injury-eyes-blood-red.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAoe() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimaryEffectChanges() {
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'system.stats.globalMods.trait',
|
||||||
|
value: -2,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
return (
|
||||||
|
super.description +
|
||||||
|
`<p>${this.data.raise ? -4 : -2} penalty to all actions involving sight.</p>
|
||||||
|
<p>Shake off attempts at end of turns with a Vigor ${this.data.strong ? '-2 ' : ''}
|
||||||
|
roll as a free action. Success removes 2 points of penalties.
|
||||||
|
A raise removes the effect.</p>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get primaryEffectButtons() {
|
||||||
|
const buttons = super.primaryEffectButtons;
|
||||||
|
const mods = [];
|
||||||
|
if (this.data.strong) {
|
||||||
|
mods.push({ label: 'Strong', value: -2 });
|
||||||
|
}
|
||||||
|
buttons.push({
|
||||||
|
label: `Shake off (Vigor${this.data.strong ? ' -2' : ''})`,
|
||||||
|
type: 'trait',
|
||||||
|
rollType: 'attribute',
|
||||||
|
rollDesc: 'Vigor',
|
||||||
|
flavor: 'Success shakes off one level, Raise shakes off two',
|
||||||
|
mods,
|
||||||
|
});
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSecondaryEffects(maintId) {
|
||||||
|
const docs = await super.createSecondaryEffects(maintId);
|
||||||
|
if (this.data.raise) {
|
||||||
|
const strong = this.data.strong;
|
||||||
|
let doc = this.createEffectDocument(this.icon, `Blinded (${strong ? 'Strong, ' : ''}Raise)`, [
|
||||||
|
{
|
||||||
|
key: 'system.stats.globalMods.trait',
|
||||||
|
value: -2,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
doc = this.enhanceSecondaryEffect(maintId, doc);
|
||||||
|
doc.description = this.description + '<p>This is the raise effect which can be shaken off separately.</p>';
|
||||||
|
docs.push(doc);
|
||||||
|
}
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push({
|
||||||
|
name: 'Strong',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 1,
|
||||||
|
id: 'strong',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
type: 'select',
|
||||||
|
default: 'none',
|
||||||
|
name: 'Area of Effect',
|
||||||
|
id: 'aoe',
|
||||||
|
epic: true,
|
||||||
|
choices: {
|
||||||
|
none: 'None',
|
||||||
|
mbt: 'Medium Blast Template',
|
||||||
|
lbt: 'Large Blast Template',
|
||||||
|
},
|
||||||
|
effects: { none: null, mbt: null, lbt: null },
|
||||||
|
values: { none: 0, mbt: 2, lbt: 3 },
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
const strong = this.data.strong;
|
||||||
|
return `Blinded${strong ? ' (Strong)' : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get chatMessageEffects() {
|
||||||
|
const list = super.chatMessageEffects;
|
||||||
|
if (this.data.aoe !== 'none') {
|
||||||
|
list.push(this.data.aoe.toUpperCase());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/module/powers/bolt.js
Normal file
94
src/module/powers/bolt.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BoltEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Bolt';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/skills/ranged/tracers-triple-orange.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDamaging() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push(
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'Damage',
|
||||||
|
value: 2,
|
||||||
|
id: 'damage',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
name: 'Disintegrate',
|
||||||
|
value: 1,
|
||||||
|
id: 'disintigrate',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Greater Bolt',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 4,
|
||||||
|
id: 'greater',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rate of Fire',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 2,
|
||||||
|
id: 'rof',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get powerPoints() {
|
||||||
|
let total = super.powerPoints;
|
||||||
|
total += this.data.aoe === 'l' ? 1 : 0;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
const dmgDie = (this.data.greater ? 4 : this.data.damage ? 3 : 2) + (this.data.raise ? 1 : 0);
|
||||||
|
let desc = super.description + '<p>';
|
||||||
|
if (this.data.rof) {
|
||||||
|
desc += `Up to two bolts (RoF 2) do ${dmgDie}d6 damage each.`;
|
||||||
|
} else {
|
||||||
|
desc += `The bolt does ${dmgDie}d6 damage.`;
|
||||||
|
}
|
||||||
|
if (this.data.disintegrate) {
|
||||||
|
desc +=
|
||||||
|
'The bolt is <em>disintegrating</em>. If being used to break' +
|
||||||
|
' something, the damage dice can Ace. A creature Incapacitated by a ' +
|
||||||
|
'disintegrating bolt must make a Vigor roll or its body turns to dust';
|
||||||
|
}
|
||||||
|
desc += '</p>';
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
221
src/module/powers/boostLowerTrait.js
Normal file
221
src/module/powers/boostLowerTrait.js
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import { moduleName } from '../globals.js';
|
||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BoostLowerTraitEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Boost/Lower Trait';
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAdditionalRecipients() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientCost() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return this?.data?.direction === 'Boost'
|
||||||
|
? 'icons/magic/life/cross-embers-glow-yellow-purple.webp'
|
||||||
|
: 'icons/magic/movement/chevrons-down-yellow.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return this?.data?.direction === 'Boost' ? 5 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimaryEffectChanges() {
|
||||||
|
let modValue = '2';
|
||||||
|
if (this.data.raise) {
|
||||||
|
modValue = '4';
|
||||||
|
}
|
||||||
|
modValue = (this.data.direction === 'Boost' ? '+' : '-') + modValue;
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: this.data.trait.diekey,
|
||||||
|
value: modValue,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (this.data.direction === 'Lower' && this.data.greater) {
|
||||||
|
changes.push({
|
||||||
|
key: this.data.trait.modkey,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: -2,
|
||||||
|
priority: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSecondaryEffects(maintId) {
|
||||||
|
const docs = await super.createSecondaryEffects(maintId);
|
||||||
|
if (this.data.raise && this.data.direction === 'Lower') {
|
||||||
|
const name = 'major ' + this.effectName;
|
||||||
|
const modValue = this.data.direction === 'Boost' ? '+2' : '-2';
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: this.data.trait.diekey,
|
||||||
|
value: modValue,
|
||||||
|
priority: 0,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const doc = this.enhanceSecondaryEffect(maintId, this.createEffectDocument(this.icon, name, changes));
|
||||||
|
doc.description = this.description + '<p>This is the raise effect which can be shaken off separately.</p>';
|
||||||
|
docs.push(doc);
|
||||||
|
}
|
||||||
|
return docs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
let name = `${this.data.direction} ${this.data.trait.name}`;
|
||||||
|
const nameMods = [];
|
||||||
|
if (this.data.greater) {
|
||||||
|
nameMods.push('Greater');
|
||||||
|
}
|
||||||
|
if (this.data.direction === 'Lower' && this.data.strong) {
|
||||||
|
nameMods.push('Strong');
|
||||||
|
}
|
||||||
|
if (nameMods.length > 0) {
|
||||||
|
name += ` (${nameMods.join(', ')})`;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let desc = super.description;
|
||||||
|
const amount = `${this.data.raise ? 2 : 1} die type${this.data.raise ? 's' : ''}`;
|
||||||
|
desc += `<p>${this.data.direction === 'Boost' ? 'Raise' : 'Lower'} the
|
||||||
|
target's ${this.data.trait.name} die type ${amount}.`;
|
||||||
|
if (this.data.greater) {
|
||||||
|
if (this.data.direction === 'Boost') {
|
||||||
|
desc += ` Additionally, the target gains a free ${this.data.trait.name}
|
||||||
|
reroll once per ${this.data.raise ? 'action' : 'round'}.`;
|
||||||
|
} else {
|
||||||
|
desc += ` Additionally, the target suffers a -2 penalty to their
|
||||||
|
${this.data.trait.name} rolls.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
desc += '</p>';
|
||||||
|
if (this.data.direction === 'Lower') {
|
||||||
|
desc += `<p>At the end of the target's following turns, they attempt to shake off
|
||||||
|
the affect with a Spirit${this.data.strong ? ' -2' : ''}
|
||||||
|
roll as a free action. Success reduces the effect one die type. A raise
|
||||||
|
completely shakes off the effect.</p>`;
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
get primaryEffectButtons() {
|
||||||
|
const buttons = super.primaryEffectButtons;
|
||||||
|
if (this.data.direction === 'Lower') {
|
||||||
|
const mods = [];
|
||||||
|
if (this.data.strong) {
|
||||||
|
mods.push({ label: 'Strong', value: -2 });
|
||||||
|
}
|
||||||
|
buttons.push({
|
||||||
|
label: `Shake off (Spirit${this.data.strong ? ' -2' : ''})`,
|
||||||
|
type: 'trait',
|
||||||
|
rollType: 'attribute',
|
||||||
|
rollDesc: 'Spirit',
|
||||||
|
flavor: 'Success shakes off one level, Raise shakes off two',
|
||||||
|
mods,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
let traitOptions = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor'];
|
||||||
|
const allSkills = new Set();
|
||||||
|
const traits = {};
|
||||||
|
for (const traitName of traitOptions) {
|
||||||
|
const lower = traitName.toLowerCase();
|
||||||
|
traits[traitName] = {
|
||||||
|
name: traitName,
|
||||||
|
type: 'attribute',
|
||||||
|
modkey: `system.attributes.${lower}.die.modifier`,
|
||||||
|
diekey: `system.attributes.${lower}.die.sides`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (const token of this.targets) {
|
||||||
|
const skills = token.actor.items.filter((item) => item.type === 'skill');
|
||||||
|
for (const skill of skills) {
|
||||||
|
const name = skill.name;
|
||||||
|
traits[name] = {
|
||||||
|
name,
|
||||||
|
type: 'skill',
|
||||||
|
modkey: `@Skill{${name}}[system.die.modifier]`,
|
||||||
|
diekey: `@Skill{${name}}[system.die.sides]`,
|
||||||
|
};
|
||||||
|
if (name !== 'Unskilled') {
|
||||||
|
allSkills.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traitOptions = traitOptions.concat(Array.from(allSkills).sort());
|
||||||
|
const traitChoices = {};
|
||||||
|
const traitValues = {};
|
||||||
|
const traitEffects = {};
|
||||||
|
for (const trait of traitOptions) {
|
||||||
|
traitChoices[trait] = trait;
|
||||||
|
traitValues[trait] = 0;
|
||||||
|
traitEffects[trait] = null;
|
||||||
|
}
|
||||||
|
this.data.traits = traits;
|
||||||
|
mods.push({
|
||||||
|
sortOrder: -2,
|
||||||
|
name: 'Boost or Lower?',
|
||||||
|
id: 'direction',
|
||||||
|
type: 'radio',
|
||||||
|
default: 'Boost',
|
||||||
|
epic: false,
|
||||||
|
choices: { Boost: 'Boost', Lower: 'Lower' },
|
||||||
|
effects: { Boost: null, Lower: null },
|
||||||
|
values: { Boost: 0, Lower: 0 },
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
sortOrder: -1,
|
||||||
|
name: 'Trait',
|
||||||
|
id: 'traitName',
|
||||||
|
type: 'select',
|
||||||
|
epic: false,
|
||||||
|
choices: traitChoices,
|
||||||
|
values: traitValues,
|
||||||
|
effects: traitEffects,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Greater Boost/Lower Trailt',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 2,
|
||||||
|
id: 'greater',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
mods.push({
|
||||||
|
name: 'Strong (lower only)',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 1,
|
||||||
|
id: 'strong',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
async parseValues() {
|
||||||
|
await super.parseValues();
|
||||||
|
this.data.trait = this.data.traits[this.data.traitName];
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/module/powers/burrow.js
Normal file
78
src/module/powers/burrow.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BurrowEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Burrow';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/earth/projectile-stone-landslide.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAdditionalRecipients() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get additionalRecipientCost() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push({
|
||||||
|
name: 'Power',
|
||||||
|
type: 'checkbox',
|
||||||
|
id: 'power',
|
||||||
|
value: 1,
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
});
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimaryEffectChanges() {
|
||||||
|
return [
|
||||||
|
...super.getPrimaryEffectChanges(),
|
||||||
|
{
|
||||||
|
key: 'system.pace.burrow',
|
||||||
|
value: 6,
|
||||||
|
mode: CONST.ACTIVE_EFFECT_MODES.OVERRIDE,
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async primaryDocForTarget(doc, target) {
|
||||||
|
const newDoc = await super.primaryDocForTarget(doc, target);
|
||||||
|
var pace = target.actor.system.pace[target.actor.system.pace.base];
|
||||||
|
pace = this.data.raise ? pace : pace / 2;
|
||||||
|
newDoc.changes[newDoc.changes.length - 1].value = pace;
|
||||||
|
return newDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
get effectName() {
|
||||||
|
return `${this.name} ${this.data.power ? '[Power] ' : ''}` + `(${this.data.raise ? 'full' : 'half'} pace)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let text =
|
||||||
|
super.description +
|
||||||
|
`<p>Meld into the ground. Move at ${this.data.raise ? 'full' : 'half'} pace. May not run.</p>`;
|
||||||
|
if (this.data.power) {
|
||||||
|
text += '<p>Can <em>burrow</em> through solid stone, concrete, etc</p>';
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/module/powers/burst.js
Normal file
63
src/module/powers/burst.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { PowerEffect } from './basePowers.js';
|
||||||
|
|
||||||
|
export class BurstEffect extends PowerEffect {
|
||||||
|
get name() {
|
||||||
|
return 'Burst';
|
||||||
|
}
|
||||||
|
|
||||||
|
get icon() {
|
||||||
|
return 'icons/magic/sonic/projectile-shock-wave-blue.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
get duration() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTargeted() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usePrimaryEffect() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDamaging() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basePowerPoints() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasAoe() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get modifiers() {
|
||||||
|
const mods = super.modifiers;
|
||||||
|
mods.push(
|
||||||
|
{
|
||||||
|
name: 'Damage',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 2,
|
||||||
|
id: 'damage',
|
||||||
|
epic: false,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Greater Burst',
|
||||||
|
type: 'checkbox',
|
||||||
|
value: 4,
|
||||||
|
id: 'greater',
|
||||||
|
epic: true,
|
||||||
|
effect: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
const dmgDie = (this.data.greater ? 4 : this.data.damage ? 3 : 2) + (this.data.raise ? 1 : 0);
|
||||||
|
return super.description + `<p>The burst covers a Cone or Stream template and does ${dmgDie}d6 damage</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user