Compare commits
No commits in common. "main" and "fix-mirror-self" have entirely different histories.
main
...
fix-mirror
@ -1,9 +0,0 @@
|
|||||||
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
Normal file
15
.eslintrc.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true
|
||||||
|
},
|
||||||
|
extends: 'standard',
|
||||||
|
overrides: [
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
}
|
||||||
|
}
|
||||||
6
.gitattributes
vendored
6
.gitattributes
vendored
@ -1,8 +1,4 @@
|
|||||||
packs/**/*.ldb binary
|
packs/** binary
|
||||||
packs/**/MANIFEST-* binary
|
|
||||||
packs/**/CURRENT binary
|
|
||||||
packs/**/LOCK binary
|
|
||||||
packs/**/LOG* binary
|
|
||||||
*.webp filter=lfs diff=lfs merge=lfs -text
|
*.webp filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
146
.gitignore
vendored
146
.gitignore
vendored
@ -1,32 +1,132 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 Johannes Loher
|
# ---> Node
|
||||||
#
|
# Logs
|
||||||
# SPDX-License-Identifier: MIT
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# IDE
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
.idea/
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
.vs/
|
|
||||||
|
|
||||||
# Node Modules
|
# Runtime data
|
||||||
|
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/
|
||||||
npm-debug.log
|
jspm_packages/
|
||||||
|
|
||||||
# yarn2
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
.yarn/*
|
web_modules/
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
.pnp.*
|
|
||||||
|
|
||||||
# Local configuration
|
# TypeScript cache
|
||||||
foundryconfig.json
|
*.tsbuildinfo
|
||||||
|
|
||||||
# Distribution files
|
# Optional npm cache directory
|
||||||
dist
|
.npm
|
||||||
|
|
||||||
# ESLint
|
# Optional eslint cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|
||||||
# Junit results
|
# Optional stylelint cache
|
||||||
junit.xml
|
.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
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# 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
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"flags": {
|
|
||||||
"gulpfile": "gulpfile.mjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 Johannes Loher
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
dist
|
|
||||||
package-lock.json
|
|
||||||
.pnp.cjs
|
|
||||||
.pnp.js
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
semi: true,
|
|
||||||
trailingComma: 'all',
|
|
||||||
singleQuote: true,
|
|
||||||
printWidth: 120,
|
|
||||||
tabWidth: 2,
|
|
||||||
};
|
|
||||||
307
CHANGELOG.md
307
CHANGELOG.md
@ -5,270 +5,9 @@ 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]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
## 2.0.0
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Changed visual parameters for blindsense
|
|
||||||
|
|
||||||
## [2.3.4] 2024-01-29
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added additional vision mode: blindsense
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Changed setTokenVison module to detect blindsense
|
|
||||||
|
|
||||||
## [2.3.3] 2024-01-28
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Added mutagen action
|
|
||||||
|
|
||||||
## [2.3.2] 2024-01-23
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Grabbed poison macros from SWADE for use in SWPF until I can write something
|
|
||||||
else or they appear in SWPF.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Added Dodge as a detected edge on a target for roll modifiers
|
|
||||||
|
|
||||||
## [2.3.1] 2023-12-26
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Data file for the Torch module
|
|
||||||
- Import file for Token Variant Art's global effect mappings
|
|
||||||
- Macro: Request fear check specialization macro
|
|
||||||
- Macro: Fear Table to call the new fearTable api endpoint
|
|
||||||
- API: rulesVersion property
|
|
||||||
- API: fearTable(actor) calls the relevant premium core rules module's fear
|
|
||||||
table
|
|
||||||
- API: added requestFearRollFromTokens special helper
|
|
||||||
- Trait roll hooks for:
|
|
||||||
- Glow/Shroud
|
|
||||||
- Range modifiers
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- added a summary chat message for the roll results to requested rolls.
|
|
||||||
- added a target number option to requested rolls.
|
|
||||||
|
|
||||||
## [2.3.0] 2023-12-19
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Trait and Damage Roll hooks to look for and apply modifiers for target
|
|
||||||
conditions:
|
|
||||||
- Vulnerable
|
|
||||||
- Deflection
|
|
||||||
- Arcane Protection
|
|
||||||
- Arcane Resistance
|
|
||||||
- Scale
|
|
||||||
- Gang Up
|
|
||||||
- Resistences and Weaknesses
|
|
||||||
- New Macro: Set token vision
|
|
||||||
- New Common Action: Illumination (for the darkness penalty effects)
|
|
||||||
- New macro: Quick Damage Roll
|
|
||||||
- New Vision mode: Low Light Vision
|
|
||||||
- Power Effect for Zombie
|
|
||||||
- Sample fixed request roll macro
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Vision mode visual effects changed for Basic Vision and Darkvision
|
|
||||||
- Shape Change and Summon both set the disposition for their new tokens
|
|
||||||
- Shape Change and Summon both set vision to enabled for their new tokens
|
|
||||||
|
|
||||||
## [2.2.0]
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Power Effect for Havoc
|
|
||||||
- Power Effect Macro for Havoc
|
|
||||||
- Power Effect Action for Havoc
|
|
||||||
- New Macro: Request Roll
|
|
||||||
- NEW DEPENDENCY: socketlib
|
|
||||||
- Documentation:
|
|
||||||
- API Documentation
|
|
||||||
- Request Roll macro documentation
|
|
||||||
|
|
||||||
## [2.1.0]
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Changed the Summon Ally power effect macro to handle Mirror Self a little
|
|
||||||
cleaner
|
|
||||||
- Changed the power effect macro to consider swids in addition to the item
|
|
||||||
name.
|
|
||||||
- Updates to documentation
|
|
||||||
|
|
||||||
## [2.0.0]
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -285,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Shape Change
|
- Shape Change
|
||||||
- Sloth/Speed
|
- Sloth/Speed
|
||||||
|
|
||||||
## [1.2.0]
|
## 1.2.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -294,20 +33,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- incorporated glow, shroud, hurry, and hinder power modifiers into power
|
- incorporated glow, shroud, hurry, and hinder power modifiers into power
|
||||||
effects
|
effects
|
||||||
|
|
||||||
## [1.1.0]
|
## 1.1.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- gold calculator macro for SWPF gold items
|
- gold calculator macro for SWPF gold items
|
||||||
- Actions for common rolls with links to SWPF rules
|
- Actions for common rolls with links to SWPF rules
|
||||||
|
|
||||||
## [1.0.1]
|
## 1.0.1
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Summon macro now spawns tokens with prototype token's actual dimensions
|
- Summon macro now spawns tokens with prototype token's actual dimensions
|
||||||
|
|
||||||
## [1.0.0]
|
## 1.0.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@ -322,7 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Protection and Smite macros now linked to swade system effects
|
- Protection and Smite macros now linked to swade system effects
|
||||||
|
|
||||||
## [0.9.0]
|
## 0.9.0
|
||||||
|
|
||||||
- Initial 'public' release
|
- Initial 'public' release
|
||||||
|
|
||||||
@ -330,20 +69,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- DEPENDENCY: warpgate
|
- DEPENDENCY: warpgate
|
||||||
- macro helpers for the following effects:
|
- macro helpers for the following effects:
|
||||||
- Boost/Lower Trait
|
- Boost/Lower Trait
|
||||||
- Deflection
|
- Deflection
|
||||||
- Glow
|
- Glow
|
||||||
- Hinder
|
- Hinder
|
||||||
- Hurry
|
- Hurry
|
||||||
- Lingering Damage
|
- Lingering Damage
|
||||||
- Protection
|
- Protection
|
||||||
- Shroud
|
- Shroud
|
||||||
- Smite
|
- Smite
|
||||||
- Summon
|
- Summon
|
||||||
- API helpers:
|
- API helpers:
|
||||||
- `createEffectDocument`
|
- `createEffectDocument`
|
||||||
- `createMutationWithEffect`
|
- `createMutationWithEffect`
|
||||||
- `defaultMutationOptions`
|
- `defaultMutationOptions`
|
||||||
- `getActorFolderByPath`
|
- `getActorFolderByPath`
|
||||||
- `getActorsInFolder`
|
- `getActorsInFolder`
|
||||||
- `runOnTargetOrSelectedTokens`
|
- `runOnTargetOrSelectedTokens`
|
||||||
|
|||||||
73
README.md
73
README.md
@ -3,76 +3,3 @@
|
|||||||
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,23 +0,0 @@
|
|||||||
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
12
fvtt-pack.sh
@ -1,12 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
#!/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
183
gulpfile.mjs
@ -1,183 +0,0 @@
|
|||||||
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.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
macros/blind.js
Normal file
45
macros/blind.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Blind',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Blind' },
|
||||||
|
{ type: 'info', label: `Apply Blind to ${tokenList}` },
|
||||||
|
{ type: 'checkbox', label: 'Strong', options: false }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Raise', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const options = {
|
||||||
|
raise: (buttons === 'raise'),
|
||||||
|
strong: (!!inputs[2])
|
||||||
|
}
|
||||||
|
await createEffect(tokens, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, options) {
|
||||||
|
const effectDetails = (options.raise ? '-4' : '-2')
|
||||||
|
const effectEnd = (options.strong ? 'Vigor -2' : 'Vigor')
|
||||||
|
const effectName = `Blind (${effectDetails}) ${effectEnd} ends`
|
||||||
|
const baseEffect = CONFIG.statusEffects.find(se => se.label === 'EFFECT.StatusBlind')
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(baseEffect.icon, effectName, 1, [])
|
||||||
|
mutate.embedded.ActiveEffect[effectName].flags.core = { statusId: baseEffect.id }
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions('Blind')
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
133
macros/boost-lower-trait.js
Normal file
133
macros/boost-lower-trait.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
for (const token of tokens) {
|
||||||
|
const skills = token.actor.items.filter(item => item.type === 'skill')
|
||||||
|
for (const skill of skills) {
|
||||||
|
const name = skill.name
|
||||||
|
traits[name] = {
|
||||||
|
type: 'skill',
|
||||||
|
name,
|
||||||
|
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())
|
||||||
|
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Boost/Lower Trait',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Boost/Lower Trait' },
|
||||||
|
{ type: 'info', label: `Affected Tokens ${tokenList}` },
|
||||||
|
{ 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 }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Apply with raise', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const options = {
|
||||||
|
action: buttons,
|
||||||
|
name: inputs[2],
|
||||||
|
trait: traits[inputs[2]],
|
||||||
|
direction: inputs[4] || inputs[5],
|
||||||
|
greater: inputs[6],
|
||||||
|
strong: inputs[7]
|
||||||
|
}
|
||||||
|
createEffect(tokens, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPICON = 'icons/magic/life/cross-embers-glow-yellow-purple.webp'
|
||||||
|
const DOWNICON = 'icons/magic/movement/chevrons-down-yellow.webp'
|
||||||
|
|
||||||
|
async function createEffect (tokens, options) {
|
||||||
|
const raise = (options.action === 'raise')
|
||||||
|
const icon = (options.direction === 'Boost' ? UPICON : DOWNICON)
|
||||||
|
let namePart = `${options.direction} ${options.trait.name}`
|
||||||
|
const mode = CONST.ACTIVE_EFFECT_MODES.ADD
|
||||||
|
const mods = []
|
||||||
|
if (options.strong && options.direction === 'Lower') {
|
||||||
|
mods.push('strong')
|
||||||
|
}
|
||||||
|
if (options.greater) {
|
||||||
|
mods.push('greater')
|
||||||
|
}
|
||||||
|
if (mods.length > 0) {
|
||||||
|
namePart = `${namePart} (${mods.join(', ')})`
|
||||||
|
}
|
||||||
|
const minorEffect = swadeMBHelpers.createEffectDocument(
|
||||||
|
icon, `minor ${namePart}`, 5, [
|
||||||
|
{
|
||||||
|
key: options.trait.diekey,
|
||||||
|
mode,
|
||||||
|
value: (options.direction === 'Boost' ? '+2' : '-2'),
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
const majorEffect = swadeMBHelpers.createEffectDocument(
|
||||||
|
icon, `major ${namePart}`, 5, [
|
||||||
|
{
|
||||||
|
key: options.trait.diekey,
|
||||||
|
mode,
|
||||||
|
value: (options.direction === 'Boost' ? '+2' : '-2'),
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if (options.direction === 'Lower' && options.greater === 'Greater') {
|
||||||
|
minorEffect.changes.push({
|
||||||
|
key: options.trait.modkey,
|
||||||
|
mode,
|
||||||
|
value: '-2',
|
||||||
|
priority: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const mutate = {
|
||||||
|
embedded: {
|
||||||
|
ActiveEffect: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutate.embedded.ActiveEffect[minorEffect.id] = minorEffect
|
||||||
|
if (raise) {
|
||||||
|
mutate.embedded.ActiveEffect[majorEffect.id] = majorEffect
|
||||||
|
}
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(namePart)
|
||||||
|
for (const token of tokens) {
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
62
macros/confusion.js
Normal file
62
macros/confusion.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Confusion',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Confusion' },
|
||||||
|
{ type: 'info', label: `Apply Confusion to ${tokenList}` },
|
||||||
|
{ type: 'checkbox', label: 'Greater (adds shaken)', options: false }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Distracted', value: 'distracted' },
|
||||||
|
{ label: 'Vulnerable', value: 'vulnerable' },
|
||||||
|
{ label: 'Raise (both)', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
const greater = (inputs[2] === 'Greater (adds shaken)')
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
await createEffect(tokens, buttons, greater)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus (label, name) {
|
||||||
|
const effect = JSON.parse(JSON.stringify(
|
||||||
|
CONFIG.statusEffects.find(se => se.label === label)))
|
||||||
|
effect.label = name
|
||||||
|
effect.flags.core = { statusId: effect.id }
|
||||||
|
effect.id = name
|
||||||
|
return effect
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice, greater) {
|
||||||
|
const effects = []
|
||||||
|
if (choice === 'distracted' || choice === 'raise') {
|
||||||
|
effects.push(getStatus('SWADE.Distr', 'Distracted'))
|
||||||
|
}
|
||||||
|
if (choice === 'vulnerable' || choice === 'raise') {
|
||||||
|
effects.push(getStatus('SWADE.Vuln', 'Vulnerable'))
|
||||||
|
}
|
||||||
|
if (greater) {
|
||||||
|
effects.push(getStatus('SWADE.Shaken', 'Shaken'))
|
||||||
|
}
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions('Confusion')
|
||||||
|
const mutate = {
|
||||||
|
embedded: { ActiveEffect: {} }
|
||||||
|
}
|
||||||
|
for (const effect of effects) {
|
||||||
|
mutate.embedded.ActiveEffect[effect.id] = effect
|
||||||
|
}
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
macros/deflection.js
Normal file
37
macros/deflection.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Deflection',
|
||||||
|
content: `Apply <em>Deflection</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply (melee)', value: 'melee' },
|
||||||
|
{ label: 'Apply (ranged)', value: 'ranged' },
|
||||||
|
{ label: 'Apply with raise (both)', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions)
|
||||||
|
|
||||||
|
if (choice && choice !== 'cancel') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const icon = 'icons/magic/defensive/shield-barrier-deflect-teal.webp'
|
||||||
|
let effectName = 'Deflection'
|
||||||
|
if (choice === 'raise') {
|
||||||
|
effectName = `${effectName} (all)`
|
||||||
|
} else {
|
||||||
|
effectName = `${effectName} (${choice})`
|
||||||
|
}
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 5, [])
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
macros/entangle.js
Normal file
74
macros/entangle.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Entangle',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Entangle' },
|
||||||
|
{ type: 'info', label: `Apply Entangle to ${tokenList}` },
|
||||||
|
{ 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 }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Entangled', value: 'apply' },
|
||||||
|
{ label: 'Bound (raise)', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const options = {
|
||||||
|
apply: (buttons === 'raise' ? 'bound' : 'entangled'),
|
||||||
|
damage: (inputs[3] ? '2d4' : (inputs[4] ? '2d6' : null)),
|
||||||
|
tough: (!!inputs[5])
|
||||||
|
}
|
||||||
|
await createEffect(tokens, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatus (label, name) {
|
||||||
|
const effect = JSON.parse(JSON.stringify(
|
||||||
|
CONFIG.statusEffects.find(se => se.label === label)))
|
||||||
|
effect.label = name
|
||||||
|
if (!effect.flags) {
|
||||||
|
effect.flags = {}
|
||||||
|
}
|
||||||
|
effect.flags.core = { statusId: effect.id }
|
||||||
|
effect.id = name
|
||||||
|
return effect
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, options) {
|
||||||
|
const effectSearch = (options.apply === 'bound' ? 'SWADE.Bound' : 'SWADE.Entangled')
|
||||||
|
const effectName = (options.apply === 'bound' ? 'Bound' : 'Entangled')
|
||||||
|
const effect = getStatus(effectSearch, effectName)
|
||||||
|
const extraIcon = 'icons/magic/nature/root-vine-barrier-wall-brown.webp'
|
||||||
|
const extraEffect = swadeMBHelpers.createEffectDocument(extraIcon, 'Entangle Modifier', 1, [])
|
||||||
|
|
||||||
|
if (options.damage) {
|
||||||
|
extraEffect.id = `${extraEffect.id} - ${options.damage} dmg`
|
||||||
|
extraEffect.label = `${extraEffect.label} - ${options.damage} dmg`
|
||||||
|
}
|
||||||
|
if (options.tough) {
|
||||||
|
extraEffect.id = `Tough ${extraEffect.id}`
|
||||||
|
extraEffect.label = `Tough ${extraEffect.label}`
|
||||||
|
}
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions('Entangle')
|
||||||
|
const mutate = { embedded: { ActiveEffect: {} } }
|
||||||
|
mutate.embedded.ActiveEffect[effect.id] = effect
|
||||||
|
if (options.damage || options.tough) {
|
||||||
|
mutate.embedded.ActiveEffect[extraEffect.id] = extraEffect
|
||||||
|
}
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
58
macros/glow.js
Normal file
58
macros/glow.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Glow',
|
||||||
|
content: `Apply <em>Glow</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'OK', value: 'ok' },
|
||||||
|
{ label: 'Mutate token lighting', value: 'mutate' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions)
|
||||||
|
|
||||||
|
if (choice === 'ok' || choice === 'mutate') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const icon = 'icons/magic/light/explosion-star-blue-large.webp'
|
||||||
|
const effectName = 'Glow'
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 5, [])
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
if (choice === 'mutate') {
|
||||||
|
const mutate2 = {
|
||||||
|
token: {
|
||||||
|
light: {
|
||||||
|
alpha: 0.5,
|
||||||
|
angle: 360,
|
||||||
|
attenuation: 0.5,
|
||||||
|
animation: {
|
||||||
|
intensity: 5,
|
||||||
|
reverse: false,
|
||||||
|
speed: 5,
|
||||||
|
type: 'starlight'
|
||||||
|
},
|
||||||
|
bright: 0,
|
||||||
|
color: '#0f3fff',
|
||||||
|
coloration: 1,
|
||||||
|
contrast: 0,
|
||||||
|
dim: 0.5,
|
||||||
|
luminosity: 0.5,
|
||||||
|
saturation: 0,
|
||||||
|
shadows: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutateOptions.permanent = false
|
||||||
|
await warpgate.mutate(token.document, mutate2, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
|
||||||
for (const token of tokens) {
|
|
||||||
const actor = token.actor;
|
|
||||||
let total = 0;
|
|
||||||
for (const item of actor.items.filter((i) => currencies.indexOf(i.name) > -1)) {
|
|
||||||
total += item.system.price * item.system.quantity;
|
|
||||||
}
|
|
||||||
template += `<tr><td>${actor.name}</td><td>${fmt.format(total)}</td></tr>`;
|
|
||||||
}
|
}
|
||||||
template += '</thead></tbody>';
|
const fmt = Intl.NumberFormat('en-US', fmtOptions)
|
||||||
foundry.applications.api.DialogV2.prompt({
|
for (const token of tokens) {
|
||||||
window: { title: 'Currency Totals' },
|
const actor = token.actor
|
||||||
content: template,
|
let total = 0
|
||||||
});
|
for (const item of actor.items.filter(i => currencies.indexOf(i.name) > -1)) {
|
||||||
|
total += item.system.price * item.system.quantity
|
||||||
|
}
|
||||||
|
template += `<tr><td>${actor.name}</td><td>${fmt.format::(total)}</td></tr>`
|
||||||
|
}
|
||||||
|
template += '</thead></tbody>'
|
||||||
|
Dialog.prompt({
|
||||||
|
title: 'Currency Totals',
|
||||||
|
content: template
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
38
macros/hinder.js
Normal file
38
macros/hinder.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Hinder',
|
||||||
|
content: `Apply <em>Hinder</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'OK', value: 'ok' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions, 'column')
|
||||||
|
|
||||||
|
if (choice === 'ok') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const icon = 'icons/magic/movement/abstract-ribbons-red-orange.webp'
|
||||||
|
const effectName = 'Hinder'
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'system.stats.speed.value',
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: -2,
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 5, changes)
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
macros/hurry.js
Normal file
38
macros/hurry.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Hurry',
|
||||||
|
content: `Apply <em>Hurry</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'OK', value: 'ok' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions, 'column')
|
||||||
|
|
||||||
|
if (choice === 'ok') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const icon = 'icons/skills/movement/feet-winged-boots-blue.webp'
|
||||||
|
const effectName = 'Hurry'
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'system.stats.speed.value',
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: 2,
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 5, changes)
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
41
macros/intangibility.js
Normal file
41
macros/intangibility.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Intangibility',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Intangibility' },
|
||||||
|
{ type: 'info', label: `Apply Intangibility to ${tokenList}` },
|
||||||
|
{ type: 'checkbox', label: 'Duration', options: false }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const options = {
|
||||||
|
duration: (!!inputs[2])
|
||||||
|
}
|
||||||
|
await createEffect(tokens, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, options) {
|
||||||
|
const effectName = 'Intangibility'
|
||||||
|
const duration = (options.duration ? 5 * 6 * 60 : 5)
|
||||||
|
const icon = 'icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp'
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, duration, [])
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions('Intangibility')
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
44
macros/invisibility.js
Normal file
44
macros/invisibility.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Invisibility',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Invisibility' },
|
||||||
|
{ type: 'info', label: `Apply Invisibility to ${tokenList}` },
|
||||||
|
{ type: 'checkbox', label: 'Duration', options: false }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Raise', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const options = {
|
||||||
|
raise: (buttons === 'raise'),
|
||||||
|
duration: (!!inputs[2])
|
||||||
|
}
|
||||||
|
await createEffect(tokens, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, options) {
|
||||||
|
const effectName = `${options.raise ? 'major' : 'minor'} Invisibility`
|
||||||
|
const baseEffect = CONFIG.statusEffects.find(se => se.label === 'EFFECT.StatusInvisible')
|
||||||
|
const duration = (options.duration ? 5 * 6 * 60 : 5)
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(baseEffect.icon, effectName, duration, [])
|
||||||
|
mutate.embedded.ActiveEffect[effectName].flags.core = { statusId: baseEffect.id }
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions('Invisibility')
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
32
macros/lingering-damage.js
Normal file
32
macros/lingering-damage.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Lingering Damage',
|
||||||
|
content: `Apply <em>Lingering Damage</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Ok', value: 'ok' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions)
|
||||||
|
|
||||||
|
if (choice === 'ok') {
|
||||||
|
await createEffect(tokens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens) {
|
||||||
|
const icon = 'icons/magic/acid/dissolve-arm-flesh.webp'
|
||||||
|
const effectName = 'Lingering Damage'
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 1, [])
|
||||||
|
mutate.embedded.ActiveEffect['Lingering Damage'].flags.swade.expiration =
|
||||||
|
CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.StartOfTurnPrompt
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
game.modules.get('swade-mb-helpers').api.powerEffectsMenu({
|
|
||||||
token,
|
|
||||||
targets: game.user.targets,
|
|
||||||
});
|
|
||||||
45
macros/protection.js
Normal file
45
macros/protection.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Protection',
|
||||||
|
content: `Apply <em>Protection</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply (+2 armor)', value: 'apply' },
|
||||||
|
{ label: 'Apply with raise (+2 toughness)', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions)
|
||||||
|
|
||||||
|
if (choice && choice !== 'cancel') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const baseEffect = CONFIG.statusEffects.find(se => se.label === 'SWADE.Protection')
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: 'system.stats.toughness.armor',
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.UPGRADE,
|
||||||
|
value: 2,
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
let effectName = 'minor Protection'
|
||||||
|
if (choice === 'raise') {
|
||||||
|
changes[0].key = 'system.stats.toughness.value'
|
||||||
|
effectName = 'major Protection'
|
||||||
|
}
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(
|
||||||
|
baseEffect.icon, effectName, 5, changes)
|
||||||
|
mutate.embedded.ActiveEffect[effectName].flags.core = { statusId: baseEffect.id }
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,43 +0,0 @@
|
|||||||
new foundry.applications.api.DialogV2({
|
|
||||||
window: { title: "Damage Roll Configuration" },
|
|
||||||
content: `
|
|
||||||
<form>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Damage Roll:</label>
|
|
||||||
<input type="text" name="damageRoll" value="2d4x" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>AP:</label>
|
|
||||||
<input type="number" name="ap" value="0" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Flavor:</label>
|
|
||||||
<input type="text" name="flavor" value="" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
action: "ok",
|
|
||||||
label: "Roll Damage",
|
|
||||||
callback: (event, button, dialog) => {
|
|
||||||
const form = new foundry.applications.ux.FormDataExtended(button.form);
|
|
||||||
console.log(form)
|
|
||||||
const damageRoll = form.object.damageRoll;
|
|
||||||
let flavor = form.object.flavor;
|
|
||||||
const ap = parseInt(form.object.ap) || 0;
|
|
||||||
const options = {};
|
|
||||||
if (ap > 0) {
|
|
||||||
flavor = `${flavor ? flavor + " - " : ""}AP: ${ap}`
|
|
||||||
options.ap = ap;
|
|
||||||
}
|
|
||||||
// Perform the damage roll and send the message
|
|
||||||
new CONFIG.Dice.DamageRoll(damageRoll, null, options).toMessage({ flavor });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "cancel",
|
|
||||||
label: "Cancel",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).render(true);
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
const requestFearRollFromTokens = game.modules.get('swade-mb-helpers').api.requestFearRollFromTokens;
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let tokens = Array.from(game.user.targets);
|
|
||||||
if (tokens.length < 1) {
|
|
||||||
tokens = canvas.tokens.controlled;
|
|
||||||
}
|
|
||||||
if (tokens.length < 1) {
|
|
||||||
ui.notifications.error('Please target or select some tokens');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new foundry.applications.api.DialogV2({
|
|
||||||
window: { title: 'Request Fear roll...' },
|
|
||||||
content: `
|
|
||||||
<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: [
|
|
||||||
{
|
|
||||||
action: "submit",
|
|
||||||
label: 'Request Roll',
|
|
||||||
callback: (event, button, dialog) => {
|
|
||||||
formdata = new foundry.applications.ux.FormDataExtended(button.form).object
|
|
||||||
const fear = parseInt(formdata.fear) || 0;
|
|
||||||
const options = { targetNumber: 4, fear };
|
|
||||||
requestFearRollFromTokens(tokens, options);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "cancel", label: 'Cancel',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
const requestRollFromTokens = game.modules.get('swade-mb-helpers').api.requestRollFromTokens;
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
let tokens = Array.from(game.user.targets);
|
|
||||||
if (tokens.length < 1) {
|
|
||||||
tokens = canvas.tokens.controlled;
|
|
||||||
}
|
|
||||||
if (tokens.length < 1) {
|
|
||||||
ui.notifications.error('Please target or select some tokens');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributes = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor'];
|
|
||||||
const skillSet = new Set();
|
|
||||||
for (const token of tokens) {
|
|
||||||
const tokenSkills = token.actor.items.filter(
|
|
||||||
(i) => i.type === 'skill' && !['Untrained', 'Untrained Attempt'].includes(i.name),
|
|
||||||
);
|
|
||||||
for (const skill of tokenSkills) {
|
|
||||||
skillSet.add(skill.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const attributeOptions = attributes
|
|
||||||
.map(
|
|
||||||
(a) => `
|
|
||||||
<option ${a === 'Agility' ? 'selected ' : ''} value="a|${a}">${a}</option>`,
|
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
const skillOptions = Array.from(skillSet)
|
|
||||||
.sort()
|
|
||||||
.map(
|
|
||||||
(s) => `
|
|
||||||
<option value="s|${s}">${s}</option>`,
|
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
const content = `
|
|
||||||
<form>
|
|
||||||
<p>Requesting roll from ${tokens.map((t) => t.name).join(', ')}.</p>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="trait">Trait to roll</label>
|
|
||||||
<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) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
// 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,156 +0,0 @@
|
|||||||
const argBright = typeof args === 'undefined' ? null : args.length > 0 ? args[0] : null;
|
|
||||||
// argument can be one of 'bright', 'dim', 'dark', 'pitchdark'. Other values
|
|
||||||
// will guess based on scene darkness
|
|
||||||
const BRIGHT_LEVELS = ['bright', 'dim', 'dark', 'pitchdark'];
|
|
||||||
const THRESHOLDS = {
|
|
||||||
dim: 0.4,
|
|
||||||
dark: 0.6,
|
|
||||||
pitchdark: 0.8,
|
|
||||||
};
|
|
||||||
const RANGES = {
|
|
||||||
basic: {
|
|
||||||
bright: 25,
|
|
||||||
dim: 25,
|
|
||||||
dark: 10,
|
|
||||||
pitchdark: 0,
|
|
||||||
},
|
|
||||||
lowlight: {
|
|
||||||
bright: 25,
|
|
||||||
dim: 25,
|
|
||||||
dark: 10,
|
|
||||||
pitchdark: 0,
|
|
||||||
},
|
|
||||||
darkvision: {
|
|
||||||
bright: 25,
|
|
||||||
dim: 25,
|
|
||||||
dark: 10,
|
|
||||||
pitchdark: 10,
|
|
||||||
},
|
|
||||||
nightvision: {
|
|
||||||
bright: 200,
|
|
||||||
dim: 200,
|
|
||||||
dark: 200,
|
|
||||||
pitchdark: 200,
|
|
||||||
},
|
|
||||||
blindsense: {
|
|
||||||
bright: 5,
|
|
||||||
dim: 5,
|
|
||||||
dark: 5,
|
|
||||||
pitchdark: 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const SIGHT_NAMES = {
|
|
||||||
lowlight: 'low-light-vision',
|
|
||||||
darkvision: 'darkvision',
|
|
||||||
nightvision: 'night-vision',
|
|
||||||
blindsense: 'blindsense',
|
|
||||||
};
|
|
||||||
const SIGHT_MODES = {
|
|
||||||
lowlight: 'lowlight',
|
|
||||||
darkvision: 'darkvision',
|
|
||||||
nightvision: 'darkvision',
|
|
||||||
basic: 'basic',
|
|
||||||
blindsense: 'blindsense',
|
|
||||||
};
|
|
||||||
|
|
||||||
function findAbility(token, swid) {
|
|
||||||
return token.actor.items.find((i) => i.type === 'ability' && i.system.swid === swid);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const scene = game.scenes.current;
|
|
||||||
let sceneBright = BRIGHT_LEVELS[0];
|
|
||||||
if (scene.darkness > THRESHOLDS.pitchdark) {
|
|
||||||
sceneBright = BRIGHT_LEVELS[3];
|
|
||||||
} else if (scene.darkness > THRESHOLDS.dark) {
|
|
||||||
sceneBright = BRIGHT_LEVELS[2];
|
|
||||||
} else if (scene.darkness > THRESHOLDS.dim) {
|
|
||||||
sceneBright = BRIGHT_LEVELS[1];
|
|
||||||
}
|
|
||||||
let bright = sceneBright;
|
|
||||||
if (argBright && BRIGHT_LEVELS.includes(argBright)) {
|
|
||||||
bright = argBright;
|
|
||||||
}
|
|
||||||
|
|
||||||
new foundry.applications.api.DialogV2({
|
|
||||||
window: { title: 'Select scene brightness' },
|
|
||||||
content: `
|
|
||||||
<form>
|
|
||||||
<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: [
|
|
||||||
{
|
|
||||||
action: "submit",
|
|
||||||
label: 'Select scene Brightness',
|
|
||||||
value: 'ok',
|
|
||||||
callback: (event, button, dialog) => {
|
|
||||||
const form = button.form;
|
|
||||||
const formDataObject = new foundry.applications.ux.FormDataExtended(form).object;
|
|
||||||
console.log('form data', formDataObject, form);
|
|
||||||
|
|
||||||
bright = formDataObject.bright;
|
|
||||||
for (const tokenId of scene.tokens.map((t) => t.id)) {
|
|
||||||
const token = scene.tokens.get(tokenId);
|
|
||||||
if (!token.sight.enabled) {
|
|
||||||
console.log(`Skipping ${token.name}, vision not enabled`);
|
|
||||||
continue;
|
|
||||||
// don't set sight on a token where it's not enabled
|
|
||||||
}
|
|
||||||
let sightType = 'basic';
|
|
||||||
for (const sight in SIGHT_NAMES) {
|
|
||||||
if (findAbility(token, SIGHT_NAMES[sight])) {
|
|
||||||
sightType = sight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const range = RANGES[sightType][bright];
|
|
||||||
const sightMode = SIGHT_MODES[sightType];
|
|
||||||
const visionModeData = CONFIG.Canvas.visionModes[sightMode].vision.defaults;
|
|
||||||
const data = {
|
|
||||||
'sight.range': range,
|
|
||||||
'sight.visionMode': sightMode,
|
|
||||||
'sight.attenuation': visionModeData.attenuation,
|
|
||||||
'sight.brightness': visionModeData.brightness,
|
|
||||||
'sight.saturation': visionModeData.saturation,
|
|
||||||
'sight.contrast': visionModeData.contrast,
|
|
||||||
};
|
|
||||||
console.log(`Updating ${token.name}:`, sightType, bright, data);
|
|
||||||
token.update(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ action: "cancel", label: 'Cancel' },
|
|
||||||
],
|
|
||||||
}).render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
58
macros/shroud.js
Normal file
58
macros/shroud.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const dialogOptions = {
|
||||||
|
title: 'Shroud',
|
||||||
|
content: `Apply <em>Shroud</em> to ${tokenList}`,
|
||||||
|
default: 'cancel',
|
||||||
|
buttons: [
|
||||||
|
{ label: 'OK', value: 'ok' },
|
||||||
|
{ label: 'Mutate token lighting', value: 'mutate' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = await warpgate.buttonDialog(dialogOptions)
|
||||||
|
|
||||||
|
if (choice === 'ok' || choice === 'mutate') {
|
||||||
|
await createEffect(tokens, choice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, choice) {
|
||||||
|
const icon = 'icons/magic/perception/silhouette-stealth-shadow.webp'
|
||||||
|
const effectName = 'Shroud'
|
||||||
|
for (const token of tokens) {
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(icon, effectName, 5, [])
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
if (choice === 'mutate') {
|
||||||
|
const mutate2 = {
|
||||||
|
token: {
|
||||||
|
light: {
|
||||||
|
alpha: 0.5,
|
||||||
|
angle: 360,
|
||||||
|
attenuation: 0.1,
|
||||||
|
animation: {
|
||||||
|
intensity: 5,
|
||||||
|
reverse: false,
|
||||||
|
speed: 5,
|
||||||
|
type: 'roiling'
|
||||||
|
},
|
||||||
|
bright: 0,
|
||||||
|
color: null,
|
||||||
|
coloration: 0,
|
||||||
|
contrast: 0,
|
||||||
|
dim: 0.1,
|
||||||
|
luminosity: -0.15,
|
||||||
|
saturation: 0,
|
||||||
|
shadows: 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutateOptions.permanent = false
|
||||||
|
await warpgate.mutate(token.document, mutate2, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
macros/smite.js
Normal file
74
macros/smite.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const tokenList = tokens.map(t => t.name).join(', ')
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Smite',
|
||||||
|
defaultButton: 'Cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Smite' },
|
||||||
|
{ type: 'info', label: `Apply Smite to ${tokenList}` },
|
||||||
|
{ type: 'checkbox', label: 'Greater', options: false }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Apply with Raise', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenWeapons = {}
|
||||||
|
let index = 2
|
||||||
|
for (const token of tokens) {
|
||||||
|
index += 2
|
||||||
|
tokenWeapons[token.id] = index
|
||||||
|
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>' })
|
||||||
|
menuData.inputs.push({ type: 'select', label: token.name, options: weapons })
|
||||||
|
}
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
for (const tokenId in tokenWeapons) {
|
||||||
|
tokenWeapons[tokenId] = inputs[tokenWeapons[tokenId]]
|
||||||
|
}
|
||||||
|
const greater = (inputs[2] === 'Greater')
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
await createEffect(tokens, tokenWeapons, buttons, greater)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEffect (tokens, tokenWeapons, choice, greater) {
|
||||||
|
const baseEffect = CONFIG.statusEffects.find(se => se.label === 'SWADE.Smite')
|
||||||
|
const effectIcon = baseEffect.icon
|
||||||
|
let changeValue = (choice === 'raise' ? '+4' : '+2')
|
||||||
|
if (greater) {
|
||||||
|
changeValue = (choice === 'raise' ? '+6' : '+4')
|
||||||
|
}
|
||||||
|
for (const token of tokens) {
|
||||||
|
const weaponName = tokenWeapons[token.id]
|
||||||
|
const weaponId = token.actor.items.getName(weaponName)?.id
|
||||||
|
const changeKey = `@Weapon{${weaponName}}[system.damage]`
|
||||||
|
if (!weaponId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const effectName = `${choice === 'raise' ? 'major' : 'minor'} Smite${greater ? '(greater)' : ''} (${weaponName})`
|
||||||
|
const changes = [
|
||||||
|
{
|
||||||
|
key: changeKey,
|
||||||
|
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
|
||||||
|
value: changeValue,
|
||||||
|
priority: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const mutate = swadeMBHelpers.createMutationWithEffect(
|
||||||
|
effectIcon, effectName, 5, changes)
|
||||||
|
mutate.embedded.ActiveEffect[effectName].flags.core = { statusId: baseEffect.id }
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, mutate, {}, mutateOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
macros/summon.js
Normal file
74
macros/summon.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
const ACTORFOLDER = 'Summonables'
|
||||||
|
const SUMMONICON = 'icons/magic/symbols/runes-star-orange.webp'
|
||||||
|
|
||||||
|
swadeMBHelpers.runOnTargetOrSelectedTokens(main)
|
||||||
|
|
||||||
|
async function main (tokens) {
|
||||||
|
const token = tokens[0]
|
||||||
|
const tokenList = token.name
|
||||||
|
const folder = swadeMBHelpers.getActorFolderByPath(ACTORFOLDER)
|
||||||
|
const actors = swadeMBHelpers.getActorsInFolder(folder)
|
||||||
|
const menuOptions = {
|
||||||
|
title: 'Summon Creature',
|
||||||
|
defaultButton: 'cancel',
|
||||||
|
options: {}
|
||||||
|
}
|
||||||
|
const menuData = {
|
||||||
|
inputs: [
|
||||||
|
{ type: 'header', label: 'Summon Creature' },
|
||||||
|
{ type: 'info', label: `${tokenList} is summoning` },
|
||||||
|
{
|
||||||
|
type: 'select',
|
||||||
|
label: 'Ally to summon',
|
||||||
|
options: Object.keys(actors).sort().map(k => { return { value: actors[k].id, html: k } })
|
||||||
|
},
|
||||||
|
{ type: 'number', label: 'Number to spawn (+half base cost per)', options: 1 }
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{ label: 'Apply', value: 'apply' },
|
||||||
|
{ label: 'Apply with raise', value: 'raise' },
|
||||||
|
{ label: 'Cancel', value: 'cancel' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const { buttons, inputs } = await warpgate.menu(menuData, menuOptions)
|
||||||
|
if (buttons && buttons !== 'cancel') {
|
||||||
|
const summonData = {
|
||||||
|
raise: (buttons === 'raise'),
|
||||||
|
actorId: inputs[2],
|
||||||
|
number: inputs[3]
|
||||||
|
}
|
||||||
|
summonData.actor = game.actors.get(summonData.actorId)
|
||||||
|
summonData.actorName = summonData.actor.name
|
||||||
|
summonData.icon = summonData.actor.prototypeToken.texture.src
|
||||||
|
summonData.token = summonData.actor.prototypeToken
|
||||||
|
|
||||||
|
doWork(summonData, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doWork (summonData, token) {
|
||||||
|
console.log('Summon ', token, summonData)
|
||||||
|
const effectName = `Summoned ${summonData.actorName} (${summonData.number})`
|
||||||
|
const tokenEffectMutation = swadeMBHelpers.createMutationWithEffect(SUMMONICON, effectName, 5, [])
|
||||||
|
const mutateOptions = swadeMBHelpers.defaultMutationOptions(effectName)
|
||||||
|
await warpgate.mutate(token.document, tokenEffectMutation, {}, mutateOptions)
|
||||||
|
|
||||||
|
const spawnOptions = {
|
||||||
|
controllingActor: token.actor,
|
||||||
|
duplicates: summonData.number,
|
||||||
|
comparisonKeys: { ActiveEffect: 'label' },
|
||||||
|
crosshairs: {
|
||||||
|
icon: summonData.icon,
|
||||||
|
label: `Summon ${summonData.actorName}`,
|
||||||
|
drawOutline: false,
|
||||||
|
rememberControlled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const spawnMutation = {
|
||||||
|
token: {
|
||||||
|
actorLink: false,
|
||||||
|
name: `${token.name}'s ${summonData.token.name}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await warpgate.spawn(summonData.actorName, spawnMutation, {}, spawnOptions)
|
||||||
|
}
|
||||||
@ -1,24 +1,19 @@
|
|||||||
{
|
{
|
||||||
"id": "swade-mb-helpers",
|
"id": "swade-mb-helpers",
|
||||||
"title": "SWADE Helpers (MB)",
|
"title": "SWADE Helpers (MB)",
|
||||||
"description": "Mike's collection of SWADE helpers",
|
"description": "Mike's collection of swade helpers",
|
||||||
|
"version": "2.0.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Mike",
|
"name": "Mike"
|
||||||
"flags": {}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
|
||||||
"version": "4.1.0",
|
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "11",
|
||||||
"verified": "13"
|
"verified": "11"
|
||||||
},
|
},
|
||||||
"esmodules": [
|
"esmodules": [
|
||||||
"module/swade-mb-helpers.js"
|
"scripts/module.js"
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"styles/swade-mb-helpers.css"
|
|
||||||
],
|
],
|
||||||
"packs": [
|
"packs": [
|
||||||
{
|
{
|
||||||
@ -44,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "CommonActions",
|
"name": "Common Actions",
|
||||||
"label": "SWADE MB Common Actions",
|
"label": "SWADE MB Common Actions",
|
||||||
"path": "packs/common-actions",
|
"path": "packs/common-actions",
|
||||||
"type": "Item",
|
"type": "Item",
|
||||||
@ -59,33 +54,11 @@
|
|||||||
"label": "SWADE MB Helper Actors",
|
"label": "SWADE MB Helper Actors",
|
||||||
"path": "packs/helper-actors",
|
"path": "packs/helper-actors",
|
||||||
"type": "Actor",
|
"type": "Actor",
|
||||||
"system": "swade",
|
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"PLAYER": "OBSERVER",
|
"PLAYER": "OBSERVER",
|
||||||
"ASSISTANT": "OWNER"
|
"ASSISTANT": "OWNER"
|
||||||
}
|
},
|
||||||
},
|
"system": "swade"
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"label": "SWADE MB Gear",
|
|
||||||
"path": "packs/gear",
|
|
||||||
"type": "Item",
|
|
||||||
"system": "swade",
|
|
||||||
"ownership": {
|
|
||||||
"PLAYER": "OBSERVER",
|
|
||||||
"ASSISTANT": "OWNER"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packFolders": [
|
"packFolders": [
|
||||||
@ -98,9 +71,7 @@
|
|||||||
"module-docs",
|
"module-docs",
|
||||||
"helper-macros",
|
"helper-macros",
|
||||||
"helper-actors",
|
"helper-actors",
|
||||||
"Common Actions",
|
"Common Actions"
|
||||||
"swade-mb-gear",
|
|
||||||
"power-actors"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -111,55 +82,36 @@
|
|||||||
"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": {
|
||||||
"minimum": "5.1.0",
|
"verified": "2.2.5"
|
||||||
"verified": "5.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requires": [
|
"requires": [
|
||||||
{
|
{
|
||||||
"id": "socketlib",
|
"id": "warpgate",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"compatibility": {}
|
"manifest": "https://github.com/trioderegion/warpgate/releases/latest/download/module.json",
|
||||||
},
|
"compatibility": {
|
||||||
{
|
"verified": "1.16.2"
|
||||||
"id": "tcal",
|
}
|
||||||
"type": "module",
|
|
||||||
"compatibility": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "sequencer",
|
|
||||||
"type": "module",
|
|
||||||
"compatibility": {}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"recommends": [
|
"recommends": [
|
||||||
|
{
|
||||||
|
"id": "token-variants",
|
||||||
|
"type": "module",
|
||||||
|
"compatibility": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "torch",
|
"id": "torch",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"compatibility": {}
|
"compatibility": {}
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "JB2A_DnD5e",
|
|
||||||
"type": "module",
|
|
||||||
"compatibility": {}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "visual-active-effects",
|
|
||||||
"type": "module",
|
|
||||||
"compatibility": {}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"languages": [
|
"url": "https://git.bloy.org/foundryvtt/swade-mb-helpers",
|
||||||
{
|
"manifest": "https://git.bloy.org/foundryvtt/swade-mb-helpers/raw/branch/main/module.json",
|
||||||
"lang": "en",
|
"download": "https://git.bloy.org/foundryvtt/swade-mb-helpers/archive/main.zip",
|
||||||
"name": "English",
|
"license": "./LICENSE",
|
||||||
"path": "lang/en.json",
|
"readme": "./README.md"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
14692
package-lock.json
generated
14692
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
@ -1,53 +1,11 @@
|
|||||||
{
|
{
|
||||||
"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": {
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@league-of-foundry-developers/foundry-vtt-types": "^9.280.0",
|
||||||
"@rollup/stream": "^3.0.1",
|
"eslint": "^8.48.0",
|
||||||
"@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0",
|
"eslint-config-standard": "^17.1.0",
|
||||||
"eslint": "^9.2.0",
|
"eslint-plugin-import": "^2.28.1",
|
||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-plugin-n": "^16.0.2",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
"fs-extra": "^11.2.0",
|
"typescript": "^5.2.2"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
packs/common-actions/000169.ldb
Normal file
BIN
packs/common-actions/000169.ldb
Normal file
Binary file not shown.
1
packs/common-actions/CURRENT
Normal file
1
packs/common-actions/CURRENT
Normal file
@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000170
|
||||||
8
packs/common-actions/LOG
Normal file
8
packs/common-actions/LOG
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
2023/11/07-23:29:20.343335 7f46667fc640 Recovering log #167
|
||||||
|
2023/11/07-23:29:20.345329 7f46667fc640 Delete type=0 #167
|
||||||
|
2023/11/07-23:29:20.345336 7f46667fc640 Delete type=3 #165
|
||||||
|
2023/11/07-23:29:29.393563 7f46649f4640 Level-0 table #173: started
|
||||||
|
2023/11/07-23:29:29.393574 7f46649f4640 Level-0 table #173: 0 bytes OK
|
||||||
|
2023/11/07-23:29:29.394638 7f46649f4640 Delete type=0 #171
|
||||||
|
2023/11/07-23:29:29.396575 7f46649f4640 Manual compaction at level-0 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items!xA7qKMmugJv7z6j1' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:29.396609 7f46649f4640 Manual compaction at level-1 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items!xA7qKMmugJv7z6j1' @ 0 : 0; will stop at (end)
|
||||||
15
packs/common-actions/LOG.old
Normal file
15
packs/common-actions/LOG.old
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
2023/11/07-17:50:41.783626 7f4666ffd640 Recovering log #163
|
||||||
|
2023/11/07-17:50:41.785537 7f4666ffd640 Delete type=0 #163
|
||||||
|
2023/11/07-17:50:41.785550 7f4666ffd640 Delete type=3 #161
|
||||||
|
2023/11/07-23:29:14.520879 7f46649f4640 Level-0 table #168: started
|
||||||
|
2023/11/07-23:29:14.522237 7f46649f4640 Level-0 table #168: 19378 bytes OK
|
||||||
|
2023/11/07-23:29:14.523105 7f46649f4640 Delete type=0 #166
|
||||||
|
2023/11/07-23:29:14.524999 7f46649f4640 Manual compaction at level-0 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items!xA7qKMmugJv7z6j1' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:14.526907 7f46649f4640 Manual compaction at level-1 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items!xA7qKMmugJv7z6j1' @ 0 : 0; will stop at '!items!xA7qKMmugJv7z6j1' @ 219 : 1
|
||||||
|
2023/11/07-23:29:14.526908 7f46649f4640 Compacting 1@1 + 1@2 files
|
||||||
|
2023/11/07-23:29:14.527704 7f46649f4640 Generated table #169@1: 21 keys, 9211 bytes
|
||||||
|
2023/11/07-23:29:14.527711 7f46649f4640 Compacted 1@1 + 1@2 files => 9211 bytes
|
||||||
|
2023/11/07-23:29:14.528471 7f46649f4640 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2023/11/07-23:29:14.528489 7f46649f4640 Delete type=2 #168
|
||||||
|
2023/11/07-23:29:14.528516 7f46649f4640 Delete type=2 #160
|
||||||
|
2023/11/07-23:29:14.541956 7f46649f4640 Manual compaction at level-1 from '!items!xA7qKMmugJv7z6j1' @ 219 : 1 .. '!items!xA7qKMmugJv7z6j1' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs/common-actions/MANIFEST-000170
Normal file
BIN
packs/common-actions/MANIFEST-000170
Normal file
Binary file not shown.
BIN
packs/helper-actors/000068.ldb
Normal file
BIN
packs/helper-actors/000068.ldb
Normal file
Binary file not shown.
1
packs/helper-actors/CURRENT
Normal file
1
packs/helper-actors/CURRENT
Normal file
@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000069
|
||||||
8
packs/helper-actors/LOG
Normal file
8
packs/helper-actors/LOG
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
2023/11/07-23:29:20.346979 7f4665ffb640 Recovering log #66
|
||||||
|
2023/11/07-23:29:20.349569 7f4665ffb640 Delete type=3 #64
|
||||||
|
2023/11/07-23:29:20.349612 7f4665ffb640 Delete type=0 #66
|
||||||
|
2023/11/07-23:29:29.395544 7f46649f4640 Level-0 table #72: started
|
||||||
|
2023/11/07-23:29:29.395555 7f46649f4640 Level-0 table #72: 0 bytes OK
|
||||||
|
2023/11/07-23:29:29.396547 7f46649f4640 Delete type=0 #70
|
||||||
|
2023/11/07-23:29:29.396590 7f46649f4640 Manual compaction at level-0 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:29.396624 7f46649f4640 Manual compaction at level-1 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
|
||||||
15
packs/helper-actors/LOG.old
Normal file
15
packs/helper-actors/LOG.old
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
2023/11/07-17:50:41.786664 7f4665ffb640 Recovering log #62
|
||||||
|
2023/11/07-17:50:41.788546 7f4665ffb640 Delete type=0 #62
|
||||||
|
2023/11/07-17:50:41.788559 7f4665ffb640 Delete type=3 #60
|
||||||
|
2023/11/07-23:29:14.523160 7f46649f4640 Level-0 table #67: started
|
||||||
|
2023/11/07-23:29:14.524102 7f46649f4640 Level-0 table #67: 1737 bytes OK
|
||||||
|
2023/11/07-23:29:14.524964 7f46649f4640 Delete type=0 #65
|
||||||
|
2023/11/07-23:29:14.525003 7f46649f4640 Manual compaction at level-0 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:14.525014 7f46649f4640 Manual compaction at level-1 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at '!actors!U5v4gFHquo0Y1SAq' @ 2 : 1
|
||||||
|
2023/11/07-23:29:14.525016 7f46649f4640 Compacting 1@1 + 1@2 files
|
||||||
|
2023/11/07-23:29:14.526092 7f46649f4640 Generated table #68@1: 1 keys, 1737 bytes
|
||||||
|
2023/11/07-23:29:14.526097 7f46649f4640 Compacted 1@1 + 1@2 files => 1737 bytes
|
||||||
|
2023/11/07-23:29:14.526856 7f46649f4640 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2023/11/07-23:29:14.526872 7f46649f4640 Delete type=2 #5
|
||||||
|
2023/11/07-23:29:14.526891 7f46649f4640 Delete type=2 #67
|
||||||
|
2023/11/07-23:29:14.541950 7f46649f4640 Manual compaction at level-1 from '!actors!U5v4gFHquo0Y1SAq' @ 2 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs/helper-actors/MANIFEST-000069
Normal file
BIN
packs/helper-actors/MANIFEST-000069
Normal file
Binary file not shown.
BIN
packs/helper-macros/000190.ldb
Normal file
BIN
packs/helper-macros/000190.ldb
Normal file
Binary file not shown.
1
packs/helper-macros/CURRENT
Normal file
1
packs/helper-macros/CURRENT
Normal file
@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000199
|
||||||
8
packs/helper-macros/LOG
Normal file
8
packs/helper-macros/LOG
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
2023/11/07-23:29:20.340266 7f4665ffb640 Recovering log #197
|
||||||
|
2023/11/07-23:29:20.342378 7f4665ffb640 Delete type=0 #197
|
||||||
|
2023/11/07-23:29:20.342408 7f4665ffb640 Delete type=3 #195
|
||||||
|
2023/11/07-23:29:29.392493 7f46649f4640 Level-0 table #202: started
|
||||||
|
2023/11/07-23:29:29.392505 7f46649f4640 Level-0 table #202: 0 bytes OK
|
||||||
|
2023/11/07-23:29:29.393529 7f46649f4640 Delete type=0 #200
|
||||||
|
2023/11/07-23:29:29.394661 7f46649f4640 Manual compaction at level-0 from '!folders!hIbrWxg1nDutCSwt' @ 72057594037927935 : 1 .. '!macros!wU2mAUnw3RW9qMT8' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:29.396569 7f46649f4640 Manual compaction at level-1 from '!folders!hIbrWxg1nDutCSwt' @ 72057594037927935 : 1 .. '!macros!wU2mAUnw3RW9qMT8' @ 0 : 0; will stop at (end)
|
||||||
8
packs/helper-macros/LOG.old
Normal file
8
packs/helper-macros/LOG.old
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
2023/11/07-17:50:41.780718 7f4665ffb640 Recovering log #193
|
||||||
|
2023/11/07-17:50:41.782723 7f4665ffb640 Delete type=3 #191
|
||||||
|
2023/11/07-17:50:41.782737 7f4665ffb640 Delete type=0 #193
|
||||||
|
2023/11/07-23:29:14.517951 7f46649f4640 Level-0 table #198: started
|
||||||
|
2023/11/07-23:29:14.517966 7f46649f4640 Level-0 table #198: 0 bytes OK
|
||||||
|
2023/11/07-23:29:14.518928 7f46649f4640 Delete type=0 #196
|
||||||
|
2023/11/07-23:29:14.520867 7f46649f4640 Manual compaction at level-0 from '!folders!hIbrWxg1nDutCSwt' @ 72057594037927935 : 1 .. '!macros!wU2mAUnw3RW9qMT8' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:14.524993 7f46649f4640 Manual compaction at level-1 from '!folders!hIbrWxg1nDutCSwt' @ 72057594037927935 : 1 .. '!macros!wU2mAUnw3RW9qMT8' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs/helper-macros/MANIFEST-000199
Normal file
BIN
packs/helper-macros/MANIFEST-000199
Normal file
Binary file not shown.
BIN
packs/module-docs/000196.ldb
Normal file
BIN
packs/module-docs/000196.ldb
Normal file
Binary file not shown.
1
packs/module-docs/CURRENT
Normal file
1
packs/module-docs/CURRENT
Normal file
@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000197
|
||||||
8
packs/module-docs/LOG
Normal file
8
packs/module-docs/LOG
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
2023/11/07-23:29:20.336710 7f46667fc640 Recovering log #194
|
||||||
|
2023/11/07-23:29:20.338673 7f46667fc640 Delete type=0 #194
|
||||||
|
2023/11/07-23:29:20.338682 7f46667fc640 Delete type=3 #192
|
||||||
|
2023/11/07-23:29:29.390257 7f46649f4640 Level-0 table #200: started
|
||||||
|
2023/11/07-23:29:29.390274 7f46649f4640 Level-0 table #200: 0 bytes OK
|
||||||
|
2023/11/07-23:29:29.391163 7f46649f4640 Delete type=0 #198
|
||||||
|
2023/11/07-23:29:29.392479 7f46649f4640 Manual compaction at level-0 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:29.393557 7f46649f4640 Manual compaction at level-1 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 0 : 0; will stop at (end)
|
||||||
15
packs/module-docs/LOG.old
Normal file
15
packs/module-docs/LOG.old
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
2023/11/07-17:50:41.777838 7f4666ffd640 Recovering log #190
|
||||||
|
2023/11/07-17:50:41.779631 7f4666ffd640 Delete type=0 #190
|
||||||
|
2023/11/07-17:50:41.779649 7f4666ffd640 Delete type=3 #188
|
||||||
|
2023/11/07-23:29:14.515315 7f46649f4640 Level-0 table #195: started
|
||||||
|
2023/11/07-23:29:14.516677 7f46649f4640 Level-0 table #195: 9634 bytes OK
|
||||||
|
2023/11/07-23:29:14.517872 7f46649f4640 Delete type=0 #193
|
||||||
|
2023/11/07-23:29:14.518953 7f46649f4640 Manual compaction at level-0 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 0 : 0; will stop at (end)
|
||||||
|
2023/11/07-23:29:14.518974 7f46649f4640 Manual compaction at level-1 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 0 : 0; will stop at '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 98 : 1
|
||||||
|
2023/11/07-23:29:14.518976 7f46649f4640 Compacting 1@1 + 1@2 files
|
||||||
|
2023/11/07-23:29:14.519779 7f46649f4640 Generated table #196@1: 6 keys, 4503 bytes
|
||||||
|
2023/11/07-23:29:14.519792 7f46649f4640 Compacted 1@1 + 1@2 files => 4503 bytes
|
||||||
|
2023/11/07-23:29:14.520666 7f46649f4640 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||||
|
2023/11/07-23:29:14.520737 7f46649f4640 Delete type=2 #167
|
||||||
|
2023/11/07-23:29:14.520824 7f46649f4640 Delete type=2 #195
|
||||||
|
2023/11/07-23:29:14.524986 7f46649f4640 Manual compaction at level-1 from '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 98 : 1 .. '!journal.pages!Mw1g2Fx5dp4SoqVP.lhULHNp4gz9IjOR3' @ 0 : 0; will stop at (end)
|
||||||
BIN
packs/module-docs/MANIFEST-000197
Normal file
BIN
packs/module-docs/MANIFEST-000197
Normal file
Binary file not shown.
80
release.sh
80
release.sh
@ -1,80 +0,0 @@
|
|||||||
#!/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}" \
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
// 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()],
|
|
||||||
});
|
|
||||||
18
scripts/api.js
Normal file
18
scripts/api.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { helpers } from './helpers.js'
|
||||||
|
import { shim, log } from './shim.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 = {
|
||||||
|
DEBUG: true,
|
||||||
|
powerEffects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
scripts/helpers.js
Normal file
92
scripts/helpers.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { CONST, shim } from './shim.js'
|
||||||
|
|
||||||
|
export class helpers {
|
||||||
|
static runOnTargetOrSelectedTokens (runFunc) {
|
||||||
|
let tokens = []
|
||||||
|
const targets = Array.from(shim.targets)
|
||||||
|
if (targets.length > 0) {
|
||||||
|
tokens = targets
|
||||||
|
} else if (shim.controlled.length > 0) {
|
||||||
|
tokens = shim.controlled
|
||||||
|
}
|
||||||
|
if (tokens.length > 0) {
|
||||||
|
runFunc(tokens)
|
||||||
|
} else {
|
||||||
|
shim.notifications.error('Please select or target a token')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static createEffectDocument (icon, name, durationRounds, changes) {
|
||||||
|
const effectData = {
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
duration: { rounds: durationRounds },
|
||||||
|
flags: {
|
||||||
|
swade: {
|
||||||
|
expiration: CONST.SWADE.STATUS_EFFECT_EXPIRATION.EndOfTurnPrompt,
|
||||||
|
loseTurnOnHold: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
return effectData
|
||||||
|
}
|
||||||
|
|
||||||
|
static createMutationWithEffect (icon, name, durationRounds, changes) {
|
||||||
|
const effect = helpers.createEffectDocument(icon, name, durationRounds, changes)
|
||||||
|
const mutate = {
|
||||||
|
embedded: { ActiveEffect: {} }
|
||||||
|
}
|
||||||
|
mutate.embedded.ActiveEffect[name] = effect
|
||||||
|
return mutate
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultMutationOptions (name) {
|
||||||
|
const mutateOptions = {
|
||||||
|
name,
|
||||||
|
permanent: true,
|
||||||
|
description: name
|
||||||
|
}
|
||||||
|
return mutateOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
static getActorFolderByPath (path) {
|
||||||
|
const names = path.split('/')
|
||||||
|
if (names[0] === '') {
|
||||||
|
names.shift()
|
||||||
|
}
|
||||||
|
let name = names.shift()
|
||||||
|
let folder = shim.folders.find(f => f.name === name && !f.folder)
|
||||||
|
while (names.length > 0) {
|
||||||
|
name = names.shift()
|
||||||
|
folder = folder.children.find(c => c.folder.name === name)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
22
scripts/module.js
Normal file
22
scripts/module.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { api } from './api.js'
|
||||||
|
import { shapeChangeOnDismiss } from './powerEffects.js'
|
||||||
|
import { log } from './shim.js'
|
||||||
|
|
||||||
|
const moduleName = 'swade-mb-helpers'
|
||||||
|
|
||||||
|
function _checkModule (name) {
|
||||||
|
if (!game.modules.get(name)?.active && game.user.isGM) {
|
||||||
|
let action = 'install and activate'
|
||||||
|
if (game.modules.get(name)) action = 'activate'
|
||||||
|
ui.notifications.error(
|
||||||
|
`SWADE MB Helpers requires the ${name} module. Please ${action} it.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooks.on('setup', api.registerFunctions)
|
||||||
|
|
||||||
|
Hooks.on('ready', () => {
|
||||||
|
_checkModule('warpgate')
|
||||||
|
log('Initialized SWADE MB Helpers')
|
||||||
|
warpgate.event.watch(warpgate.EVENT.DISMISS, shapeChangeOnDismiss)
|
||||||
|
})
|
||||||
1421
scripts/powerEffects.js
Normal file
1421
scripts/powerEffects.js
Normal file
@ -0,0 +1,1421 @@
|
|||||||
|
import { CONST, log, shim } from './shim.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 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
|
||||||
|
},
|
||||||
|
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...`
|
||||||
|
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}`
|
||||||
|
},
|
||||||
|
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.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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
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: SummonUndeadEffect
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
163
scripts/shim.js
Normal file
163
scripts/shim.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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 Actor () {
|
||||||
|
return Actor
|
||||||
|
}
|
||||||
|
|
||||||
|
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 notifications () {
|
||||||
|
return ui.notifications
|
||||||
|
}
|
||||||
|
|
||||||
|
static get actors () {
|
||||||
|
return game.actors
|
||||||
|
}
|
||||||
|
|
||||||
|
static get scenes () {
|
||||||
|
return game.scenes
|
||||||
|
}
|
||||||
|
|
||||||
|
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 warpgateMenu (menuData, menuOptions) {
|
||||||
|
return warpgate.menu(menuData, menuOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
static warpgateSpawn (...args) {
|
||||||
|
return warpgate.spawn(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
static warpgateSpawnAt (...args) {
|
||||||
|
return warpgate.spawnAt(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
@ -1,1283 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
{
|
|
||||||
"swade": {
|
|
||||||
"system": "swade",
|
|
||||||
"topology": "standard",
|
|
||||||
"quantity" : "quantity",
|
|
||||||
"aliases": {
|
|
||||||
"Candle (1 hr, 2\" radius)": "Candle",
|
|
||||||
"Torch (1 hour, 4\" radius)": "Torch",
|
|
||||||
"Lantern, bullseye (10\" cone)": "Lantern, bullseye",
|
|
||||||
"Lantern, hooded (6\" radius)": "Lantern, hooded"
|
|
||||||
},
|
|
||||||
"sources": {
|
|
||||||
"Candle (1 hr, 2\" radius)": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 0.5, "dim": 2, "angle": 360, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Everburning Torch": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 0.5, "dim": 4, "angle": 360, "color": "#4dfbc2", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Lantern, bullseye (10\" cone)": {
|
|
||||||
"states": 3,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 4, "dim": 10, "angle": 180, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bright": 0, "dim": 3, "angle": 180, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Lantern, hooded (6\" radius)": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 3, "dim": 6, "angle": 360, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Torch (1 hour, 4\" radius)": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 0.5, "dim": 4, "angle": 360, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 5, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Sunrod": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 6, "dim": 12, "angle": 360, "color": "#f9e380", "alpha": 0.15,
|
|
||||||
"animation": { "type": "sunburst", "speed": 1, "intensity": 3, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Lamp, Small (3\" radius)": {
|
|
||||||
"states": 2,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 1, "dim": 3, "angle": 360, "color": "#e9c40c", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 1, "intensity": 3, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Light / Darkness": {
|
|
||||||
"states": 3,
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 1, "dim": 3, "angle": 360, "color": "#e9c40c", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 1, "intensity": 3, "reverse": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bright": 3, "dim": 10, "angle": 120, "color": "#e68805", "alpha": 0.15,
|
|
||||||
"animation": { "type": "torch", "speed": 2, "intensity": 4, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Lantern of Revealing": {
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 1, "dim": 5, "angle": 360, "color": "#a80092", "alpha": 0.15,
|
|
||||||
"animation": { "type": "starlight", "speed": 1, "intensity": 3, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Robe of Scintillating Colors": {
|
|
||||||
"light": [
|
|
||||||
{
|
|
||||||
"bright": 3, "dim": 5, "angle": 360, "color": "#888888", "alpha": 0.15,
|
|
||||||
"animation": { "type": "rainbowswirl", "speed": 1, "intensity": 2, "reverse": false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"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,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
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 }],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
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})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,1076 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
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>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
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>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class ConfusionEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Confusion';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/control/hypnosis-mesmerism-swirl.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get usePrimaryEffect() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAoe() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
const mods = super.modifiers;
|
|
||||||
mods.push({
|
|
||||||
type: 'select',
|
|
||||||
default: 'mbt',
|
|
||||||
name: 'Area of Effect',
|
|
||||||
id: 'aoe',
|
|
||||||
epic: false,
|
|
||||||
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 },
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
name: 'Greater Confusion',
|
|
||||||
value: 2,
|
|
||||||
id: 'greater',
|
|
||||||
epic: true,
|
|
||||||
type: 'checkbox',
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
get powerPoints() {
|
|
||||||
let total = super.powerPoints;
|
|
||||||
total += this.data.aoe === 'l' ? 1 : 0;
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
get menuButtons() {
|
|
||||||
const data = [
|
|
||||||
{ label: 'Apply with Distracted', value: 'distracted' },
|
|
||||||
{ label: 'Apply with Vulnerable', value: 'vulnerable' },
|
|
||||||
{ label: 'Apply with both (raise)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' },
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseValues() {
|
|
||||||
await super.parseValues();
|
|
||||||
this.data.distracted = this.data.button === 'distracted' || this.data.button === 'raise';
|
|
||||||
this.data.vulnerable = this.data.button === 'vulnerable' || this.data.button === 'raise';
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
const size = this.data.aoe.toUpperCase();
|
|
||||||
let effect = 'Vulnerable';
|
|
||||||
if (this.data.raise) {
|
|
||||||
effect = 'both Distracted and Vulnerable';
|
|
||||||
} else if (this.data.distracted) {
|
|
||||||
effect = 'Distracted';
|
|
||||||
}
|
|
||||||
if (this.data.Greater) {
|
|
||||||
effect += ' as well as Shaken';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
super.description +
|
|
||||||
`
|
|
||||||
<p>The targets in the ${size} are ${effect}.</p>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createSecondaryEffects(maintId) {
|
|
||||||
const docs = await super.createSecondaryEffects(maintId);
|
|
||||||
if (this.data.distracted) {
|
|
||||||
PowerEffect.getStatus('SWADE.Distr', 'Distracted', false).then((v) => docs.push(v));
|
|
||||||
}
|
|
||||||
if (this.data.vulnerable) {
|
|
||||||
PowerEffect.getStatus('SWADE.Vuln', 'Vulnerable', false).then((v) => docs.push(v));
|
|
||||||
}
|
|
||||||
if (this.data.greater) {
|
|
||||||
PowerEffect.getStatus('SWADE.Shaken', 'Shaken', false).then((v) => docs.push(v));
|
|
||||||
}
|
|
||||||
return docs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class ConjureItemEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Conjure Item';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/commodities/tech/cog-steel-grey.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRaisable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'Weight in pounds OR daily rations',
|
|
||||||
type: 'number',
|
|
||||||
default: 1,
|
|
||||||
value: 0,
|
|
||||||
id: 'weight',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Complete Set',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 1,
|
|
||||||
id: 'complete',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Create Food and Water (Special)',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 0,
|
|
||||||
id: 'rations',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Durable (+1 per pound)',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 0,
|
|
||||||
id: 'durable',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get powerPoints() {
|
|
||||||
if (this.data.rations) {
|
|
||||||
return this.data.weight;
|
|
||||||
}
|
|
||||||
return this.data.weight * (this.data.durable ? 3 : 2) + (this.data.complete ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
if (this.data.rations) {
|
|
||||||
return (
|
|
||||||
super.description +
|
|
||||||
`<p>Conjure enough food and drink to feed ${this.data.weight} size 0
|
|
||||||
humanoid${this.data.weight > 1 ? 's' : ''} for 1 day. The food decays
|
|
||||||
and is inedible after 24 hours if not consumed.</p>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let desc = super.description + `<p>Conjure an item up to ${this.data.weight} in pounds. `;
|
|
||||||
if (this.data.raise) {
|
|
||||||
desc += 'It is a more durable item than usual for its type. ';
|
|
||||||
}
|
|
||||||
if (this.data.complete) {
|
|
||||||
desc += 'Whatever is conjured is a complete set. ';
|
|
||||||
}
|
|
||||||
if (this.data.durable) {
|
|
||||||
desc += 'The item remains until it is dispelled or dismissed by the caster. ';
|
|
||||||
} else {
|
|
||||||
desc += 'The item lasts for one hour. ';
|
|
||||||
}
|
|
||||||
desc += 'Once it is dismissed or expires, the item fades from existance.</p>';
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class CreatePitEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Create Pit';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/environment/traps/spike-skull-white-brown.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get usePrimaryEffect() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
return [
|
|
||||||
...super.modifiers,
|
|
||||||
{
|
|
||||||
name: 'Soft Ground',
|
|
||||||
id: 'soft',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 1,
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Spiked',
|
|
||||||
id: 'spiked',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 1,
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Deep',
|
|
||||||
id: 'deep',
|
|
||||||
type: 'checkbox',
|
|
||||||
default: false,
|
|
||||||
value: 2,
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get damage() {
|
|
||||||
let dice = 2;
|
|
||||||
let mod = 2;
|
|
||||||
if (this.data.deep) {
|
|
||||||
dice += 2;
|
|
||||||
mod = 4;
|
|
||||||
}
|
|
||||||
if (this.data.spiked) {
|
|
||||||
dice += 1;
|
|
||||||
}
|
|
||||||
return { dice, mod };
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let text = super.description;
|
|
||||||
const deep = this.data.deep ? '8" (16 yards)' : '4" (8 yards)';
|
|
||||||
const damage = this.damage;
|
|
||||||
text += `<p>An extradimension pit appears as a hole the size of an MBT,
|
|
||||||
${deep} deep.
|
|
||||||
`;
|
|
||||||
if (this.data.spiked) {
|
|
||||||
text += 'The bottom is covered in spikes.';
|
|
||||||
}
|
|
||||||
text += `</p>
|
|
||||||
<p>Anyone in or adjacent to the area must make an Evasion roll
|
|
||||||
${this.data.raise ? '(at -2 from the raise)' : ''} or fall in.
|
|
||||||
`;
|
|
||||||
if (this.data.soft) {
|
|
||||||
text += 'The bottom is soft and does no damage.';
|
|
||||||
} else {
|
|
||||||
text += `Those who fall in take ${damage.dice}d6+${damage.mod} damage.`;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
get maintEffectButtons() {
|
|
||||||
const damage = this.damage;
|
|
||||||
const buttons = super.maintEffectButtons;
|
|
||||||
if (!this.data.soft) {
|
|
||||||
buttons.push({
|
|
||||||
label: `Falling Damage (${damage.dice}d6+${damage.mod})`,
|
|
||||||
type: 'damage',
|
|
||||||
flavor: `Falling Damage${this.data.spiked ? ' with spikes!' : ''}`,
|
|
||||||
formula: `${damage.dice}d6x+${damage.mod}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class CurseEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Curse';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/control/voodoo-doll-pain-damage-purple.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 500 * 24 * 60 * 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get oneTarget() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRaisable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
const mods = super.modifiers;
|
|
||||||
mods.push({
|
|
||||||
name: 'Turn to Stone',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 5,
|
|
||||||
id: 'turntostone',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let desc = super.description;
|
|
||||||
desc += `<p>The victim must defend with a Spirit roll opposed by the
|
|
||||||
caster's arcane skill roll. Failure means the victim suffers a level
|
|
||||||
of Fatigue immediately.</p>`;
|
|
||||||
if (this.data.turntostone) {
|
|
||||||
desc += `<p>On every following run the victim must make a Spirit roll
|
|
||||||
or take a level of Fatigue. When Incapacitated, the victim turns to
|
|
||||||
stone, with a Hardness equal to his Tougness.</p>`;
|
|
||||||
} else {
|
|
||||||
desc += `<p>At sunset every day, the victim suffers a level of Fatigue.
|
|
||||||
When Incapacitated by this, he makes a Vigor roll each day to avoid
|
|
||||||
death.</p>`;
|
|
||||||
}
|
|
||||||
desc += `<p><strong>Breaking the curse:</strong> The curse can be lifted by
|
|
||||||
the original caster at will, and ends if the caster is slain. Dispel at -2
|
|
||||||
also removes the curse, but each individual may only try once.</p>`;
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DamageFieldEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Damage Field';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/defensive/shield-barrier-blades-teal.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get oneTarget() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRaisable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
const mods = super.modifiers;
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Area of Effect',
|
|
||||||
value: 2,
|
|
||||||
id: 'aoe',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Damage',
|
|
||||||
value: 2,
|
|
||||||
id: 'damage',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Greater Damage Field',
|
|
||||||
value: 4,
|
|
||||||
id: 'greater',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Mobile',
|
|
||||||
value: 2,
|
|
||||||
id: 'mobile',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
get primaryEffectButtons() {
|
|
||||||
const buttons = super.primaryEffectButtons;
|
|
||||||
let damage = '2d4';
|
|
||||||
if (this.data.greater) {
|
|
||||||
damage = '3d6';
|
|
||||||
} else if (this.data.damage) {
|
|
||||||
damage = '2d6';
|
|
||||||
}
|
|
||||||
buttons.push({
|
|
||||||
label: `Damage (${damage})`,
|
|
||||||
type: 'damage',
|
|
||||||
formula: `${damage}x`,
|
|
||||||
});
|
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let desc = super.description;
|
|
||||||
let area = 'all adjacent creatures';
|
|
||||||
let damage = '2d4';
|
|
||||||
if (this.data.greater) {
|
|
||||||
damage = '3d6 (heavy weapon)';
|
|
||||||
} else if (this.data.damage) {
|
|
||||||
damage = '2d6';
|
|
||||||
}
|
|
||||||
if (this.data.aoe) {
|
|
||||||
area = 'all creatures within a MBT';
|
|
||||||
}
|
|
||||||
desc += `<p>At the end of the recipient's turn, ${area}
|
|
||||||
automatically take ${damage} damage.`;
|
|
||||||
if (this.data.mobile) {
|
|
||||||
desc += `The caster may detach the damage field from the recipient and
|
|
||||||
move it up to his Smarts die type each round, as a limited free action.`;
|
|
||||||
}
|
|
||||||
desc += '</p>';
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPrimaryEffectChanges() {
|
|
||||||
const base = 'flags.swade.auras.damagefield';
|
|
||||||
const priority = 0;
|
|
||||||
const mode = foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE;
|
|
||||||
const changes = [
|
|
||||||
{ key: `${base}.enabled`, value: true, priority, mode },
|
|
||||||
{ key: `${base}.walls`, value: true, priority, mode },
|
|
||||||
{ key: `${base}.color`, value: '#ffcc00', priority, mode },
|
|
||||||
{ key: `${base}.alpha`, value: 0.1, priority, mode },
|
|
||||||
{
|
|
||||||
key: `${base}.radius`,
|
|
||||||
value: this.data.aoe ? 1.5 : 0.5,
|
|
||||||
priority,
|
|
||||||
mode,
|
|
||||||
},
|
|
||||||
{ key: `${base}.visibleTo`, value: [-1, 0, 1], priority, mode },
|
|
||||||
];
|
|
||||||
return changes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DarksightEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Darksight';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/perception/eye-ringed-glow-angry-small-teal.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAdditionalRecipients() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get additionalRecipientCost() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
const mods = super.modifiers;
|
|
||||||
mods.push({
|
|
||||||
name: 'Greater Darksight',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 2,
|
|
||||||
id: 'greater',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let desc = super.description;
|
|
||||||
desc += '<p>';
|
|
||||||
if (this.data.greater) {
|
|
||||||
desc += `Can see in all darkness, ignoring all illumination penalties and
|
|
||||||
4 points of penalties from invisible creatures`;
|
|
||||||
} else if (this.data.raise) {
|
|
||||||
desc += 'Can see in Pitch Darkness and ignore up to 6 points of illumination penalties';
|
|
||||||
} else {
|
|
||||||
desc += 'Can see in darkness and ignore 4 points of illumination penalties';
|
|
||||||
}
|
|
||||||
desc += '</p>';
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
get effectName() {
|
|
||||||
if (this.data.greater) {
|
|
||||||
return 'Greater Darksight';
|
|
||||||
} else if (this.data.raise) {
|
|
||||||
return 'Major Darksight';
|
|
||||||
} else {
|
|
||||||
return 'Darksight';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DeflectionEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Deflection';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/defensive/shield-barrier-deflect-teal.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAdditionalRecipients() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get additionalRecipientCost() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get menuButtons() {
|
|
||||||
const data = [
|
|
||||||
{ label: 'Melee', value: 'melee' },
|
|
||||||
{ label: 'Ranged', value: 'ranged' },
|
|
||||||
{ label: 'Raise (both)', value: 'raise' },
|
|
||||||
{ label: 'Cancel', value: 'cancel' },
|
|
||||||
];
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseValues() {
|
|
||||||
await super.parseValues();
|
|
||||||
this.data.affects = this.data.button === 'raise' ? 'all' : this.data.button;
|
|
||||||
}
|
|
||||||
|
|
||||||
get effectName() {
|
|
||||||
return `Deflection (${this.data.affects})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
return (
|
|
||||||
super.description +
|
|
||||||
`<p>Attackers subtract -2 from ${this.data.affects}
|
|
||||||
attacks when targeting this creature.</p>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,139 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DetectConcealArcanaEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Detect/Conceal Arcana';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return this.data.detect
|
|
||||||
? 'icons/magic/perception/third-eye-blue-red.webp'
|
|
||||||
: 'icons/magic/perception/silhouette-stealth-shadow.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return this.data.detect ? 5 : 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAdditionalRecipients() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get additionalRecipientCost() {
|
|
||||||
return (this.data?.aoe || 0) > 0 ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
const mods = super.modifiers;
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Alignment Sense (detect)',
|
|
||||||
value: 1,
|
|
||||||
id: 'alignment',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Identify (detect)',
|
|
||||||
value: 1,
|
|
||||||
id: 'identify',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
type: 'checkbox',
|
|
||||||
name: 'Strong (conceal)',
|
|
||||||
value: 1,
|
|
||||||
id: 'strong',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
sortOrder: -2,
|
|
||||||
name: 'Detect or Conceal?',
|
|
||||||
id: 'direction',
|
|
||||||
type: 'radio',
|
|
||||||
default: 'Detect',
|
|
||||||
epic: false,
|
|
||||||
choices: { Detect: 'Detect', Conceal: 'Conceal' },
|
|
||||||
effects: { Detect: null, Conceal: null },
|
|
||||||
values: { Detect: 0, Conceal: 0 },
|
|
||||||
});
|
|
||||||
mods.push({
|
|
||||||
name: 'Area of Effect (conceal)',
|
|
||||||
type: 'select',
|
|
||||||
default: 'none',
|
|
||||||
epic: false,
|
|
||||||
choices: { none: 'None', mbt: 'Medium Blast Template', lbt: 'Large Blast Template' },
|
|
||||||
effects: { none: null, mbt: null, lbt: null },
|
|
||||||
values: { none: 0, mbt: 1, lbt: 2 },
|
|
||||||
});
|
|
||||||
return mods;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAoe() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async parseValues() {
|
|
||||||
await super.parseValues();
|
|
||||||
this.data.detect = this.data.direction == 'Detect';
|
|
||||||
}
|
|
||||||
|
|
||||||
get powerPoints() {
|
|
||||||
return super.powerPoints + this.data.aoe;
|
|
||||||
}
|
|
||||||
|
|
||||||
get effectName() {
|
|
||||||
return `${this.data.detect ? 'Detect' : 'Conceal'} Arcana`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let desc = super.description;
|
|
||||||
if (this.data.detect) {
|
|
||||||
desc += `<p>The recipient can see and detect all supernatural persons,
|
|
||||||
objects, or effects in sight. This includes invisible foes, enchanted
|
|
||||||
objects, and so on.`;
|
|
||||||
if (this.data.raise) {
|
|
||||||
desc += `Since this was major Detect Arcana, the type of enchantments
|
|
||||||
is also known.`;
|
|
||||||
}
|
|
||||||
desc += `</p><p>If cast to learn more about a creature, the caster learns
|
|
||||||
active powers and arcane abilities.`;
|
|
||||||
if (this.data.raise) {
|
|
||||||
desc += `As major Detect in this mode, the caster also learns any
|
|
||||||
Weaknesses common to that creature type.`;
|
|
||||||
}
|
|
||||||
if (this.data.identify) {
|
|
||||||
desc += `<p>Items detected also give the recipient an idea of their
|
|
||||||
powers and how to activate them.</p>`;
|
|
||||||
}
|
|
||||||
if (this.data.alignment) {
|
|
||||||
desc += `<p>The recipient can also detect the presence and location
|
|
||||||
of supernatural good or evil within range, regardless of line of sight.</p>`;
|
|
||||||
}
|
|
||||||
desc += `</p><p><strong>Invisible Creatures:</strong> The recipient may
|
|
||||||
also ignore ${this.data.raise ? 'all' : 'up to 4 points of'} penalties
|
|
||||||
when attacking invisible or magically concealed foes.</p>`;
|
|
||||||
} else {
|
|
||||||
let area = 'one item or being';
|
|
||||||
if (this.data.aoe !== 0) {
|
|
||||||
area = `everything in a sphere the size of a
|
|
||||||
${this.data.aoe === 1 ? 'Medium' : 'Large'} Blast Template`;
|
|
||||||
}
|
|
||||||
desc += `<p>Conceal ${area} from the Detect Magic ability for
|
|
||||||
one hour. Attempts to <em>detect arcana</em> suffer a
|
|
||||||
${(this.data.strong ? -2 : 0) + this.data.raise ? -2 : -4} penalty.`;
|
|
||||||
}
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DisguiseEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Disguise';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/equipment/head/mask-carved-wood-white.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAdditionalRecipients() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get additionalRecipientCost() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
return [
|
|
||||||
...super.modifiers,
|
|
||||||
{
|
|
||||||
name: 'Size',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 1,
|
|
||||||
id: 'size',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
const size = this.data.size ? 'within two sizes of' : 'of the same size as';
|
|
||||||
return (
|
|
||||||
super.description +
|
|
||||||
`
|
|
||||||
<p>Assume the appearance of another person ${size} the recipient. Anyone
|
|
||||||
who has cause to doubt the disguise may make a Notice roll at ${this.data.raise ? -4 : -2}
|
|
||||||
as a free action to see through the disguise.</p>`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DispelEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Dispel';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/symbols/triangle-glowing-green.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return this?.data?.antiMagic ? 5 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get usePrimaryEffect() {
|
|
||||||
return !!this?.data?.antiMagic;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasAoe() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
return [
|
|
||||||
...super.modifiers,
|
|
||||||
{
|
|
||||||
name: 'Anti-Magic Field',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 8,
|
|
||||||
id: 'antiMagic',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
default: 'none',
|
|
||||||
name: 'Area of Effect',
|
|
||||||
id: 'aoe',
|
|
||||||
epic: false,
|
|
||||||
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 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Disenchant',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 1,
|
|
||||||
id: 'disenchant',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Multiple Powers',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 8,
|
|
||||||
id: 'multiple',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Remove Curse',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 2,
|
|
||||||
id: 'removeCurse',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get effectName() {
|
|
||||||
return this.data.antiMagic ? 'Anti-Magic Field' : super.effectName;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPrimaryEffectChanges() {
|
|
||||||
if (this.data.antiMagic) {
|
|
||||||
const base = 'flags.swade.auras.antiMagicField';
|
|
||||||
const priority = 0;
|
|
||||||
const mode = foundry.CONST.ACTIVE_EFFECT_MODES.OVERRIDE;
|
|
||||||
return [
|
|
||||||
{ key: `${base}.enabled`, value: true, priority, mode },
|
|
||||||
{ key: `${base}.walls`, value: true, priority, mode },
|
|
||||||
{ key: `${base}.color`, value: '#ff00cc', priority, mode },
|
|
||||||
{ key: `${base}.alpha`, value: 0.1, priority, mode },
|
|
||||||
{ key: `${base}.radius`, value: 1.5, priority, mode },
|
|
||||||
{ key: `${base}.visibleTo`, value: [-1, 0, 1], priority, mode },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return super.getPrimaryEffectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let text = super.description;
|
|
||||||
if (this.data.antiMagic) {
|
|
||||||
text += `<p>Magic items, effects, and powers within the anti magic field
|
|
||||||
have no effect. Summoned creatures must make a Spirit roll each round or
|
|
||||||
or take a wound.</p>`;
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
const multi = this.data.multiple || this.data.aoe;
|
|
||||||
const affected = `${multi ? 'all' : 'a single'}
|
|
||||||
${this.data.disenchant ? 'magic item' : 'power'}${multi ? 's' : ''}
|
|
||||||
${this.data.aoe === 'none' ? 'cast by or on the recipient' : 'within a ' + this.data.aoe.toUpperCase()}`;
|
|
||||||
text += `<p>Attempt to dispel ${affected}. `;
|
|
||||||
if (this.data.disenchant) {
|
|
||||||
text += `The item(s) magical abilities are negated for ${this.data.raise ? 'two rounds' : 'one round'}`;
|
|
||||||
} else {
|
|
||||||
text += `Each target must make an opposed arcane skill (spirit for Mystic Powers)
|
|
||||||
roll or have the power(s) end immediately.`;
|
|
||||||
}
|
|
||||||
if (this.data.removeCurse) {
|
|
||||||
text += `The normal -2 penalty to remove a curse is ignored.`;
|
|
||||||
} else {
|
|
||||||
text += `If the effect is a Curse, there is a -2 penalty to the dispeller's roll`;
|
|
||||||
}
|
|
||||||
text += '</p>';
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import { PowerEffect } from './basePowers.js';
|
|
||||||
|
|
||||||
export class DivinationEffect extends PowerEffect {
|
|
||||||
get name() {
|
|
||||||
return 'Divination';
|
|
||||||
}
|
|
||||||
|
|
||||||
get icon() {
|
|
||||||
return 'icons/magic/perception/orb-crystal-ball-scrying.webp';
|
|
||||||
}
|
|
||||||
|
|
||||||
get duration() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isTargeted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRaisable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
get basePowerPoints() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
get modifiers() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: 'Power (sacred ground only)',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 5,
|
|
||||||
id: 'power',
|
|
||||||
epic: true,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Sacred Ground',
|
|
||||||
type: 'checkbox',
|
|
||||||
value: 0,
|
|
||||||
id: 'sacred',
|
|
||||||
epic: false,
|
|
||||||
effect: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
get description() {
|
|
||||||
let desc = super.description;
|
|
||||||
desc += `<p>A brief conversation with a summoned spirit or entity. `;
|
|
||||||
if (this.data.sacred) {
|
|
||||||
desc += `There is a +2 to the roll due to being on sacred ground for the
|
|
||||||
summoned entity. `;
|
|
||||||
}
|
|
||||||
if (this.data.raise) {
|
|
||||||
desc += `The entity will be generally helpful and more direct than usual
|
|
||||||
in answering questions.i `;
|
|
||||||
} else {
|
|
||||||
desc += `The entity will answer questions to the best of its ability, but
|
|
||||||
usually in a vague or symbolic manner.i `;
|
|
||||||
}
|
|
||||||
if (this.data.sacred && this.data.power) {
|
|
||||||
desc += `The entity will also offer unsolicted advice or information,
|
|
||||||
according to its nature.`;
|
|
||||||
}
|
|
||||||
desc += '</p>';
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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