massive reorg to new project structure

This commit is contained in:
2024-05-11 20:49:16 -05:00
parent 4c93c0be94
commit a3fdf1c7f2
147 changed files with 3247 additions and 5899 deletions
+1
View File
@@ -0,0 +1 @@
{}
+46
View File
@@ -0,0 +1,46 @@
{
"id": "swade-mb-helpers",
"title": "<human readable title for swade-mb-helpers>",
"description": "<description of the package>",
"authors": [
{
"name": "<your name>",
"email": "<optionally your e-mail address>",
"discord": "<optionally your discord username>"
}
],
"url": "",
"license": "<please choose an appropriate license. https://choosealicense.com/ is a great place to get more information if you are unsure>",
"readme": "<optionally the URL to your readme>",
"bugs": "<optionally the URL to your issue list>",
"changelog": "<optionally the URL to your changelog>",
"version": "0.0.0",
"compatibility": {
"minimum": "10",
"verified": "10"
},
"scripts": [],
"esmodules": [
"module/swade-mb-helpers.js"
],
"styles": [
"styles/swade-mb-helpers.css"
],
"packs": [],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
}
],
"relationships": {
"systems": [],
"requires": [],
"conflicts": []
},
"socket": false,
"manifest": "",
"download": "https://host/path/to/0.0.0.zip",
"library": false
}
+23
View File
@@ -0,0 +1,23 @@
import { log, moduleHelpers } from './globals.js'
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
import { powerEffects } from './powerEffects.js'
import { powers } from './powers.js'
export class api {
static registerFunctions () {
log('SWADE MB Helpers initialized')
api.globals()
}
static globals () {
const moduleName = 'swade-mb-helpers'
game.modules.get(moduleName).api = {
rulesVersion: moduleHelpers.rulesVersion,
fearTable: moduleHelpers.fearTableHelper,
powerEffects,
powers,
requestRollFromTokens,
requestFearRollFromTokens
}
}
}
+542
View File
@@ -0,0 +1,542 @@
/* globals warpgate */
import { moduleName } from './globals.js'
const MAINTAIN_ICON = 'icons/magic/symbols/runes-star-blue.webp'
export class PowerEffect {
constructor (token, targets) {
this.source = token
this.targets = targets
this.data = {}
}
static async getStatus (label, name, favorite = true) {
const effect = deepClone(
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
}
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: { rounds: 99 },
flags: {
[moduleName]: {
powerEffect: true
},
swade: {
loseTurnOnHold: false,
expiration:
CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.StartOfTurnAuto
}
}
}
}
async applyActiveEffects (token, effectDocuments) {
const mutation = {
embedded: { ActiveEffect: {} }
}
const mutateOptions = {
permanent: true,
description: `${this.source.name} applying ${effectDocuments[effectDocuments.length - 1]?.name} to ${token.name}`
}
for (const effectDocument of effectDocuments) {
mutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
}
await warpgate.mutate(token.document, mutation, {}, mutateOptions)
}
get name () {
return 'Unknown Power'
}
get effectName () {
return this.name
}
get icon () {
return 'icons/magic/symbols/question-stone-yellow.webp'
}
get duration () {
return 5
}
get basePowerPoints () {
return 0
}
get usePrimaryEffect () {
return true
}
get hasAdditionalRecipients () {
return false
}
get isDamaging () {
return false
}
get additionalRecipientCost () {
return 0
}
get isTargeted () {
return false
}
get oneTarget () {
return false
}
get isRaisable () {
return true
}
get hasAoe () {
return false
}
get modifiers () {
const mods = []
mods.push({
name: 'Adaptable Caster',
id: 'adaptable',
value: 1,
epic: false,
effect: false
})
mods.push({
name: 'Fatigue',
id: 'fatigue',
value: 2,
epic: false,
effect: false
})
mods.push({
name: 'Glow',
id: 'glow',
value: 1,
epic: false,
effect: true,
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
}
]
})
mods.push({
name: 'Shroud',
id: 'shroud',
value: 1,
epic: false,
effect: true,
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
}
]
})
if (this.isDamaging) {
mods.push({
name: 'Heavy Weapon',
id: 'heavyweapon',
value: 2,
epic: false,
effect: false
})
}
mods.push({
name: 'Hinder',
id: 'hinder',
value: 1,
epic: false,
effect: true,
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
changes: [
{
key: 'system.stats.speed.value',
value: -2,
priority: 0,
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD
}
]
})
mods.push({
name: 'Hurry',
id: 'hurry',
value: 1,
epic: false,
effect: true,
icon: 'icons/skills/movement/feet-winged-sandals-tan.webp',
changes: [
{
key: 'system.stats.speed.value',
value: 2,
priority: 0,
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD
}
]
})
if (this.isDamaging) {
mods.push({
name: 'Lingering Damage',
id: 'lingeringdamage',
value: 2,
epic: false,
effect: false
})
}
if (this.hasAoe) {
mods.push({
name: 'Selective',
id: 'selective',
value: 1,
epic: false,
effect: false
})
}
return mods
}
get menuData () {
return {
inputs: this.menuInputs,
buttons: this.menuButtons
}
}
get menuInputs () {
const inputs = [
{ type: 'header', label: `${this.name} Effect` },
{
type: 'info',
label: `Apply ${this.name} Effect (base cost ${this.basePowerPoints} pp)`
}
]
if (this.isTargeted) {
let label = `<strong>Targets:</strong> ${this.targets.map((t) => t.name).join(',')}`
if (this.targets.length > 1 && this.hasAdditionalRecipients) {
label +=
` (${this.targets.length - 1} additional recipients ` +
`+${this.additionalRecipientCost} ea.)`
}
inputs.push({ type: 'info', label })
}
for (const mod of this.modifiers) {
inputs.push({
type: 'checkbox',
label:
`${mod.epic ? '⭐ ' : ''}${mod.name} ` +
`(${mod.value >= 0 ? '+' : ''}${mod.value})`
})
}
if (this.isDamaging) {
inputs.push({
type: 'select',
label: 'Armor Piercing',
options: [
{ html: 'None', value: 0, selected: true },
{ html: 'AP 2 (+1)', value: 1, selected: false },
{ html: 'AP 4 (+2)', value: 2, selected: false },
{ html: 'AP 6 (+3)', value: 3, selected: false }
]
})
}
inputs.push({
type: 'select',
label: 'Range',
options: [
{ html: 'Normal Range', value: 0, selected: true },
{ html: 'Range ×2 (+1)', value: 1, selected: false },
{ html: 'Range ×3 (+2)', value: 2, selected: false }
]
})
return inputs
}
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
}
get menuOptions () {
return {
title: `${this.name} Effect`,
defaultButton: 'Cancel',
options: {}
}
}
async powerEffect () {
const { buttons, inputs } = await warpgate.menu(
this.menuData,
this.menuOptions
)
if (buttons && buttons !== 'cancel') {
this.data.button = buttons
this.data.values = inputs
await this.parseValues()
await this.apply()
await this.sideEffects()
await this.chatMessage()
}
}
async parseValues () {
this.data.rawValues = deepClone(this.data.values)
this.data.raise = this.data.button === 'raise'
for (let i = 0; i < 2; i++) {
this.data.values.shift()
}
if (this.isTargeted) {
this.data.values.shift()
}
this.data.mods = new Set()
for (const mod of this.modifiers) {
const modEnabled = this.data.values.shift()
if (modEnabled) {
this.data.mods.add(mod.id)
}
}
if (this.isDamaging) {
this.data.armorPiercing = this.data.values.shift()
}
this.data.range = this.data.values.shift()
}
async createSecondaryEffects (maintId) {
const docs = []
for (const mod of this.modifiers) {
if (this.data.mods.has(mod.id) && mod.effect) {
const doc = this.createEffectDocument(mod.icon, mod.name, mod.changes)
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
} else {
doc.duration.seconds = 594
doc.flags[moduleName].maintId = maintId
}
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
}
get description () {
return ''
}
async createPrimaryEffect (maintId) {
const doc = this.createEffectDocument(
this.icon,
this.effectName,
this.getPrimaryEffectChanges()
)
doc.description += this.description
doc.flags[moduleName].maintId = maintId
doc.duration.seconds = 594
return doc
}
async createMaintainEffect (maintId) {
let icon = MAINTAIN_ICON
if (!this.usePrimaryEffect) {
icon = this.icon
}
const doc = this.createEffectDocument(
icon,
`Maintaining ${this.effectName}`,
[]
)
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.flags[moduleName].maintainingId = maintId
doc.flags[moduleName].targetIds = this.targets.map((t) => t.id)
return doc
}
// eslint-disable-next-line no-unused-vars
async secondaryDocsForTarget (docs, target) {
return deepClone(docs)
}
// eslint-disable-next-line no-unused-vars
async primaryDocForTarget (doc, target) {
const newDoc = deepClone(doc)
newDoc.flags[moduleName].maintainingId = doc.flags[moduleName].maintId
newDoc.flags[moduleName].targetIds = [target.id]
return newDoc
}
async apply () {
const maintId = randomID()
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)
}
}
}
if (this.duration > 0) {
await this.applyActiveEffects(this.source, [maintainDoc])
}
}
async sideEffects () {
if (this.data.mods.has('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) {
if (this.data.mods.has(mod.id)) {
total += mod.value
}
}
if (this.targets.length > 1 && this.hasAdditionalRecipients) {
total += (this.targets.length - 1) * this.additionalRecipientCost
}
total += this.data.range
if (this.isDamaging) {
total += this.data.armorPiercing
}
return total
}
get chatMessageEffects () {
const list = []
if (this.hasAdditionalRecipients && this.targets.length > 1) {
list.push(`${this.targets.length - 1} Additional Recipients`)
}
if (this.data.mods.has('adaptable')) {
list.push('Different Trapping (Adaptable Caster)')
}
if (this.data.mods.has('fatigue')) {
list.push('Fatigue (applied to targets')
}
if (this.data.mods.has('heavyweapon')) {
list.push('Heavy Weapon')
}
if (this.data.mods.has('lingeringdamage')) {
list.push('Lingering Damage')
}
if (this.data.mods.has('selective')) {
list.push('Selective')
}
if (this.isDamaging && this.data.armorPiercing > 0) {
list.push(`AP ${this.data.armorPiercing * 2}`)
}
if (this.data.range > 0) {
list.push(`Range ×${this.data.range + 1}`)
}
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><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 }
)
}
}
+126
View File
@@ -0,0 +1,126 @@
/* globals coreFearDialog */
/* globals swpfFearDialog */
/* globals warpgate */
export const moduleName = 'swade-mb-helpers'
export function log (...args) {
console.log('SWADE MB HELPERS |', ...args)
}
export class moduleHelpers {
static _socket = null
static get socket () {
return moduleHelpers._socket
}
static get rulesVersion () {
if (game.modules.get('swpf-core-rules')?.active) {
return 'swpf'
}
if (game.modules.get('swade-core-rules')?.active) {
return 'swade'
}
return 'system'
}
static 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 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: CONFIG.SWADE.CONST.STATUS_EFFECT_EXPIRATION.EndOfTurnPrompt
}
}
}
}
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 coreFearDialog // defined as global by the swade module
case 'swpf': return swpfFearDialog // defined as global by the swpf module
}
throw new ReferenceError('No premium module active. No fear table found')
}
}
+123
View File
@@ -0,0 +1,123 @@
/* global warpgate */
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 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 = warpgate.util.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>`,
rolls: []
}
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>')
if (roll) {
messageData.rolls.unshift(roll)
}
}
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 }
}
+112
View File
@@ -0,0 +1,112 @@
/* globals ColorAdjustmentsSamplerShader */
/* globals socketlib */
/* globals VisionMode */
/* globals warpgate */
import { api } from './api.js'
import { requestTokenRoll } from './helpers.js'
import { preDamageRollModifiers, preTraitRollModifiers } from './rollHelpers.js'
import { shapeChangeOnDismiss } from './powerEffects.js'
import { log, moduleHelpers } from './globals.js'
import { powerEffectManagementHook } from './powers.js'
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('init', () => {
log('INIT VISION')
CONFIG.Canvas.visionModes.basic = new VisionMode({
id: 'basic',
label: 'VISION.ModeBasicVision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.85, brightness: -1.0 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0, contrast: 0, saturation: -0.85, brightness: -1.0 },
preferred: true
}
})
CONFIG.Canvas.visionModes.darkvision = new VisionMode({
id: 'darkvision',
label: 'VISION.ModeDarkvision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: 0, brightness: 0.75, tint: [0.8, 0.8, 1.0] }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED },
levels: {
[VisionMode.LIGHTING_LEVELS.DIM]: VisionMode.LIGHTING_LEVELS.BRIGHT
}
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.1, contrast: 0, saturation: 0, brightness: 0.75 },
preferred: false
}
})
CONFIG.Canvas.visionModes.lowlight = new VisionMode({
id: 'lowlight',
label: 'Low Light Vision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.5, brightness: -0.2 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.1, contrast: 0, saturation: -0.5, brightness: -0.2 },
preferred: false
}
})
CONFIG.Canvas.visionModes.blindsense = new VisionMode({
id: 'blindsense',
label: 'Blindsense',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.5, brightness: -0.2 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED },
coloration: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED },
illumination: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.3, contrast: -0.5, saturation: 0.75, brightness: 0.5 },
preferred: false
}
})
})
Hooks.on('swadePreRollAttribute', preTraitRollModifiers)
Hooks.on('swadePreRollSkill', preTraitRollModifiers)
Hooks.on('swadeRollDamage', preDamageRollModifiers)
Hooks.on('deleteActiveEffect', powerEffectManagementHook)
Hooks.on('ready', () => {
_checkModule('warpgate')
_checkModule('socketlib')
log('Initialized SWADE MB Helpers')
warpgate.event.watch(warpgate.EVENT.DISMISS, shapeChangeOnDismiss)
})
Hooks.on('socketlib.ready', () => {
const _socket = socketlib.registerModule('swade-mb-helpers')
_socket.register('requestTokenRoll', requestTokenRoll)
moduleHelpers._socket = _socket
})
+1763
View File
@@ -0,0 +1,1763 @@
/* globals warpgate */
import { log, moduleHelpers } from './globals.js'
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
class PowerEffect {
constructor (token, targets) {
this.token = token
this.targets = targets
this.effectDocs = []
this.menuData = {
inputs: [
{ type: 'header', label: `${this.name} Effect` },
{ type: 'info', label: `Apply ${this.name} Effect` },
{ type: 'header', label: 'Global Modifiers' },
{ type: 'checkbox', label: 'Glow (+1)' },
{ type: 'checkbox', label: 'Shroud (+1)' },
{ type: 'checkbox', label: 'Hinder (+1)' },
{ type: 'checkbox', label: 'Hurry (+1)' },
{ type: 'header', label: '---------------' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Apply with Raise', value: 'raise' },
{ label: 'Cancel', value: 'cancel' }
]
}
this.menuOptions = {
title: `${this.name} Effect`,
defaultButton: 'Cancel',
options: {}
}
this.inputs = []
this.buttons = null
}
get name () {
return 'Unknown Power'
}
get durationRounds () {
return this.baseDurationRounds
}
get baseDurationRounds () {
return 5
}
async powerEffect () {
try {
await this.prepMenu()
} catch (e) {
log('Error preparing menu for power effect: ' + e.toString())
return
}
const { buttons, inputs } = await warpgate.menu(
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) {
moduleHelpers.applyActiveEffects(target, this.effectDocs)
}
}
static modEffectDoc (icon, name, key, value, durationRounds) {
return moduleHelpers.createEffectDocument(icon, name, durationRounds, [
{
key,
mode: foundry.CONST.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) {
ui.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 = moduleHelpers.createEffectDocument(
'icons/magic/death/skull-poison-green.webp',
`Lingering Damage (${this.name})`,
1
)
doc.flags.swade.expiration = CONFIG.SWADE.CONST.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(
moduleHelpers.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: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
value: '-2',
priority: 0
}
]
this.effectDocs.push(
moduleHelpers.createEffectDocument(
icon, `minor Blindness (Vigor ${strong ? '-2 ' : ''}ends)`,
this.durationRounds, changes))
if (raise) {
this.effectDocs.push(
moduleHelpers.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(
moduleHelpers.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 = foundry.CONST.ACTIVE_EFFECT_MODES.ADD
const modValue = (direction === 'Boost' ? '+2' : '-2')
const minorEffect = moduleHelpers.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 = moduleHelpers.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(moduleHelpers.getStatus('SWADE.Distr', 'Distracted'))
}
if (this.buttons === 'vulnerable' || this.buttons === 'raise') {
this.effectDocs.push(moduleHelpers.getStatus('SWADE.Vuln', 'Vulnerable'))
}
if (greater) {
this.effectDocs.push(moduleHelpers.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(
moduleHelpers.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(
moduleHelpers.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(moduleHelpers.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 = moduleHelpers.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 = moduleHelpers.getStatus(effectSearch, effectName)
const extraIcon = 'icons/magic/nature/root-vine-barrier-wall-brown.webp'
const extraEffect = moduleHelpers.createEffectDocument(extraIcon,
'Entangle Modifier', this.durationRounds, [])
if (damage) {
extraEffect.name = `${extraEffect.name} - ${damage} dmg`
}
if (tough) {
extraEffect.name = `Tough ${extraEffect.name}`
}
this.effectDocs.push(effect)
if (damage || tough) {
this.effectDocs.push(extraEffect)
}
}
}
class FearEffect extends TargetedPowerEffect {
get name () {
return 'Fear'
}
get baseDurationRounds () {
return 1
}
async prepResult () {
this.raise = (this.buttons === 'raise')
}
async applyResult () {
await super.applyResult()
await warpgate.wait(1000)
const options = {
title: 'Fear check!',
flavor: 'Failure: roll on the Fear Table if wildcard, Panicked if extra',
mods: []
}
if (this.raise) {
options.fear = '-2'
}
await requestFearRollFromTokens(this.targets, options)
}
}
class HavocEffect extends TargetedPowerEffect {
get name () {
return 'Havoc'
}
get baseDurationRounds () {
return 1
}
async prepResult () {
this.raise = (this.buttons === 'raise')
this.effectDocs.unshift(moduleHelpers.getStatus('SWADE.Distr', 'Distracted'))
}
async applyResult () {
await super.applyResult()
await warpgate.wait(1000)
const resistMods = function (token) {
const mods = []
if (token.actor.effects.find(e => e.name === 'Flying')) {
mods.push({ label: 'Flying', value: -2 })
}
return mods
}
const options = {
title: 'Resisting Havoc!',
flavour: 'Havoc!',
mods: [],
modCallback: resistMods
}
if (this.raise) {
options.mods.push({ label: 'vs. Raise', value: -2 })
}
await requestRollFromTokens(this.targets, 'attribute', 'strength', options)
}
}
class IntangibilityEffect extends TargetedPowerEffect {
get name () {
return 'Intangility'
}
get baseDurationRounds () {
if (!this.inputs) {
return 5
}
if (this.inputs[this.inputs.length - 1]) { // Duration
return 50
}
return 5 // no duration
}
async prepMenu () {
this.menuData.inputs.push({ type: 'checkbox', label: 'Duration', options: false })
this.menuData.buttons = [
{ label: 'Apply', value: 'apply' },
{ label: 'Cancel', value: 'cancel' }
]
}
async prepResult () {
const icon = 'icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp'
const effect = moduleHelpers.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 = moduleHelpers.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 = moduleHelpers.getStatus('SWADE.Protection', 'Protection')
effect.duration = { rounds: this.durationRounds }
const mode = foundry.CONST.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 = moduleHelpers.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 = moduleHelpers.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) {
ui.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 = game.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: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
value: '+2',
priority: 0
})
}
}
this.effectDocs.push(
moduleHelpers.createEffectDocument(
this.icon,
`Shape Change into ${this.protoDoc.name}`,
this.durationRounds, effectChanges)
)
this.spawnMutation = {
actor: {
name: `${this.targets[0].actor.name} (${this.actor.name} form)`,
system: {
attributes: {
smarts: { die: targetActor.system.attributes.smarts.die },
spirit: { die: targetActor.system.attributes.spirit.die }
},
wildcard: targetActor.system.wildcard
}
},
token: {
flags: {
'swade-mb-helpers.shapeChange.srcTokenId': this.targets[0].id
},
actorLink: false,
name: `${this.targets[0].name} (${this.protoDoc.name} form) `,
elevation: this.targets[0].document.elevation,
disposition: this.targets[0].document.disposition,
sight: {
enabled: true
}
},
embedded: { ActiveEffect: {}, Item: {} }
}
for (const doc of this.effectDocs) {
this.spawnMutation.embedded.ActiveEffect[doc.name] = doc
}
for (const doc of this.targets[0].actor.effects) {
this.spawnMutation.embedded.ActiveEffect[doc.name] = this.targets[0].actor.getEmbeddedDocument('ActiveEffect', doc.id)
}
for (const item of targetActor.items) {
if (item.type === 'skill' && ['smarts', 'spirit'].includes(item.system.attribute)) {
const doc = await this.targets[0].actor.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = doc
}
if (['power', 'edge', 'hindrance', 'action'].includes(item.type)) {
const doc = await this.targets[0].actor.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = doc
}
}
}
async applyResult () {
log('protoDoc', this.protoDoc)
log('spawnOptions', this.spawnOptions)
log('spawnMutation', this.spawnMutation)
const newTokenId = (await warpgate.spawnAt(
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 = moduleHelpers.getStatus('SWADE.Smite', 'Smite')
}
async applyResult () {
const mode = foundry.CONST.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 moduleHelpers.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 = moduleHelpers.createEffectDocument(
icon, name, this.durationRounds, [
{
key: 'system.stats.speed.value',
mode: foundry.CONST.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 = moduleHelpers.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 = moduleHelpers.getActorsInFolder(folder)
for (const key in folderActors) {
actors[key] = folderActors[key]
}
}
return actors
}
async prepMenu () {
this.menuData.inputs[1].label = `${this.token.name} is summoning...`
this.menuData.buttons = this.menuData.buttons.filter(b => b.value !== 'raise')
const actors = await this.prepActors()
if (Object.keys(actors).length < 1) {
ui.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 = game.actors.get(this.actorId)
this.icon = this.actor.prototypeToken.texture.src
this.protoDoc = await this.actor.getTokenDocument()
this.increasedTrait = !!(this.inputs[this.inputIndex + 2])
this.inputIndex += 3
this.spawnOptions = {
controllingActor: this.token.actor,
duplicates: this.number,
updateOpts: {
embedded: {
Item: {
renderSheet: null
}
}
},
crosshairs: {
icon: this.icon,
label: `Summon ${this.actor.name}`,
drawOutline: true,
rememberControlled: true
}
}
this.spawnMutation = {
actor: {
name: `${this.token.name}'s ${this.actor.name}`
},
token: {
actorLink: false,
name: `${this.token.name}'s ${this.protoDoc.name}`,
disposition: this.token.document.disposition,
sight: {
enabled: true
}
},
embedded: { ActiveEffect: {}, Item: {} }
}
if (this.raise && ('raise_template' in this.summonableActors)) {
const raiseTemplate = this.summonableActors.raise_template
for (const item of raiseTemplate.items) {
const raiseItemDoc = await raiseTemplate.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = raiseItemDoc
}
}
for (const effectDocument of this.effectDocs) {
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
}
}
async prepAdditional () {
if (!this.increasedTrait) {
return
}
const traitMenuOptions = {
title: `${this.name} Summon Trait Increase`,
defaultButton: 'Cancel',
options: {}
}
const skillSet = new Set()
for (const skill of this.actor.items.filter(i => i.type === 'skill')) {
skillSet.add(skill.name)
}
for (const item of Object.values(this.spawnMutation.embedded.Item).filter(i => i.type === 'skill')) {
skillSet.add(item.name)
}
const skillList = Array.from(skillSet)
const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
skillList.sort()
const traitMenuData = {
inputs: [
{ type: 'header', label: 'Increase Attributes (+1 each)' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Increase no traits', value: 'cancel' }
]
}
traitMenuData.inputs = traitMenuData.inputs.concat(
attrList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
traitMenuData.inputs.push({ type: 'header', label: 'Increase Skills (+1 each)' })
traitMenuData.inputs = traitMenuData.inputs.concat(
skillList.map((x) => { return { type: 'checkbox', label: x, options: false } }))
const { buttons, inputs } = await warpgate.menu(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 = moduleHelpers.createEffectDocument(
this.ICON, 'Increased Trait', this.durationRounds)
effectDoc.changes = modKeys.map(key => {
return {
key, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD, value: '+2', priority: 0
}
})
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
}
async applyResult () {
await this.prepAdditional()
await warpgate.spawn(this.protoDoc, this.spawnMutation, {}, this.spawnOptions)
log('protoDoc', this.protoDoc)
log('spawnOptions', this.spawnOptions)
log('spawnMutation', this.spawnMutation)
}
}
class SummonAllyEffect extends SummonEffect {
get name () {
return 'Summon Ally'
}
async prepMenu () {
await super.prepMenu()
this.menuData.buttons = [
this.menuData.buttons[0],
{ label: 'Apply with Raise', value: 'raise' },
this.menuData.buttons[1]
]
this.menuData.inputs = this.menuData.inputs.concat([
{
type: 'checkbox',
label: 'Bite/Claw (+1)',
options: false
}, {
type: 'checkbox',
label: 'Up to 3 Combat Edges (+1 per)',
options: false
}, {
type: 'checkbox',
label: 'Flight (+3)',
options: false
}
])
}
async prepResult () {
await super.prepResult()
this.biteClaw = !!(this.inputs[this.inputIndex])
this.combatEdge = !!(this.inputs[this.inputIndex + 1])
this.flight = !!(this.inputs[this.inputIndex + 2])
await this.prepMirrorSelf()
}
async prepAdditional () {
await super.prepAdditional()
await this.prepBiteClaw()
await this.prepFlight()
await this.prepCombatEdge()
}
async prepCombatEdge () {
if (!this.combatEdge || !('combat-edge_template' in this.summonableActors)) {
return
}
const template = this.summonableActors['combat-edge_template']
const edges = template.items.filter(i => i.type === 'edge').map(i => i.name)
edges.sort()
edges.unshift('None')
const edgeMenuData = {
inputs: [
{ type: 'header', label: 'Choose Edges (+1 per choice)' },
{ type: 'select', label: 'Edge 1', options: edges },
{ type: 'select', label: 'Edge 2', options: edges },
{ type: 'select', label: 'Edge 3', options: edges }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Add no edges', value: 'cancel' }
]
}
const edgeMenuOptions = {
title: `${this.name} Combat Edge Selection`,
defaultButton: 'Cancel',
options: {}
}
const { buttons, inputs } = await warpgate.menu(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 = 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: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
value: '-2',
priority: 0
})
}
}
this.spawnMutation.embedded.ActiveEffect['Mirror Self'] =
moduleHelpers.createEffectDocument(this.ICON, 'Mirror Self',
this.durationRounds, effectChanges)
}
}
class SpiritualWeaponEffect extends SummonEffect {
get name () {
return 'Spiritual Weapon'
}
async prepMenu () {
await super.prepMenu()
this.menuData.buttons = [
this.menuData.buttons[0],
{ label: 'Apply with Raise', value: 'raise' },
this.menuData.buttons[1]
]
this.menuData.inputs.pop()
this.menuData.inputs.pop()
this.menuData.inputs[this.menuData.inputs.length - 1].label = 'Weapon to summon'
const arcaneSkills = this.token.actor.items
.filter(i => i.type === 'skill').filter(s => (
s.system.swid === 'faith' ||
s.system.swid === 'spellcasting' ||
s.system.swid === 'performance')).map(s => {
return {
value: s.system.swid,
html: s.name
}
})
this.menuData.inputs.push(
{ type: 'select', label: 'Arcane Skill', options: arcaneSkills }
)
}
async prepResult () {
this.raise = (this.buttons === 'raise')
this.actorId = this.inputs[this.inputIndex]
this.arcaneSkillSwid = this.inputs[this.inputIndex + 1]
this.actor = game.actors.get(this.actorId)
this.icon = 'icons/weapons/hammers/hammer-double-glowing-yellow.webp'
this.protoDoc = await this.actor.getTokenDocument()
this.spawnOptions = {
controllingActor: this.token.actor,
duplicates: 1,
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}`,
system: {
wildcard: this.token.actor.system.wildcard,
attributes: {
spirit: {
die: {
sides: this.token.actor.system.attributes.spirit.die.sides,
modifier: this.token.actor.system.attributes.spirit.die.modifier
},
'wild-die': {
sides: this.token.actor.system.attributes.spirit['wild-die'].sides
}
}
}
}
},
token: {
actorLink: false,
name: `${this.token.name}'s ${this.protoDoc.name}`,
disposition: this.token.document.disposition,
sight: { enabled: false }
},
embedded: { ActiveEffect: {}, Item: {} }
}
const weaponItemId = this.actor.items.find(i =>
i.type === 'weapon' && i.system.swid === 'spiritual-weapon').id
const weaponDoc = this.actor.getEmbeddedDocument('Item', weaponItemId).clone()
const arcaneSkill = this.token.actor.items.find(i => i.type === 'skill' && i.system.swid === this.arcaneSkillSwid)
const arcaneDoc = this.token.actor.getEmbeddedDocument('Item', arcaneSkill.id).clone()
this.spawnMutation.embedded.Item[weaponDoc.name] = {
system: {
damage: '@spi+d4',
actions: {
trait: arcaneDoc.name
}
}
}
// weaponDoc.system.actions.trait = arcaneDoc.name
if (this.raise) {
this.spawnMutation.embedded.Item[weaponDoc.name].system.damage = '@spi+d6'
// weaponDoc.system.damage = '@spi+d6'
}
this.spawnMutation.embedded.Item[arcaneDoc.name] = arcaneDoc
// this.spawnMutation.embedded.Item[weaponDoc.name] = weaponDoc
for (const effectDocument of this.effectDocs) {
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
}
}
}
class SummonAnimalEffect extends SummonEffect {
get name () {
return 'Summon Animal'
}
}
class SummonMonsterEffect extends SummonEffect {
get name () {
return 'Summon Monster'
}
}
class SummonNaturesAllyEffect extends SummonEffect {
get name () {
return "Summon Nature's Ally"
}
}
class SummonPlanarAllyEffect extends SummonEffect {
get name () {
return 'Summon Planar Ally'
}
}
class SummonUndeadEffect extends SummonEffect {
get name () {
return 'Summon Undead'
}
}
class ZombieEffect extends SummonEffect {
get name () {
return 'Zombie'
}
async prepMenu () {
await super.prepMenu()
this.menuData.buttons = [
this.menuData.buttons[0],
{ label: 'Apply with Raise', value: 'raise' },
this.menuData.buttons[1]
]
this.menuData.inputs.pop()
this.menuData.inputs = this.menuData.inputs.concat([
{
type: 'checkbox',
label: 'Armed (Hand Weapon (Str+d6) or Ranged Weapon (2d6)',
options: false
}, {
type: 'checkbox',
label: '+2 Armor',
options: false
}, {
type: 'info',
label: 'Skeletal creatures +1 per zombie'
}
])
}
async prepResult () {
this.raise = (this.buttons === 'raise')
this.actorId = this.inputs[this.inputIndex]
this.number = this.inputs[this.inputIndex + 1]
this.actor = game.actors.get(this.actorId)
this.icon = this.actor.prototypeToken.texture.src
this.protoDoc = await this.actor.getTokenDocument()
this.increasedTrait = this.raise
this.armed = this.inputs[this.inputIndex + 2]
this.armor = this.inputs[this.inputIndex + 3]
this.spawnOptions = {
controllingActor: this.token.actor,
duplicates: this.number,
updateOpts: {
embedded: {
Item: { renderSheet: null }
}
},
crosshairs: {
icon: this.icon,
label: `Summon ${this.actor.name}`,
drawOutline: true,
rememberControlled: true
}
}
this.spawnMutation = {
actor: {
name: `${this.token.name}'s ${this.actor.name}`
},
token: {
actorLink: false,
name: `${this.token.name}'s ${this.protoDoc.name}`,
disposition: this.token.document.disposition,
sight: { enabled: true }
},
embedded: { ActiveEffect: {}, Item: {} }
}
if (this.armed && ('armed_template' in this.summonableActors)) {
const armedTemplate = this.summonableActors.armed_template
for (const item of armedTemplate.items) {
const itemDoc = await armedTemplate.getEmbeddedDocument('Item', item.id)
this.spawnMutation.embedded.Item[item.name] = itemDoc
}
}
if (this.armor) {
const effectDoc = moduleHelpers.createEffectDocument(
'icons/equipment/chest/breastplate-layered-leather-stitched.webp',
'Rotting Armor',
0)
delete effectDoc.duration
delete effectDoc.flags.swade.expiration
effectDoc.changes = [{
key: 'system.stats.toughness.armor',
mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD,
value: '+2',
priority: 0
}]
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
}
for (const effectDocument of this.effectDocs) {
this.spawnMutation.embedded.ActiveEffect[effectDocument.name] = effectDocument
}
}
async prepAdditional () {
if (!this.increasedTrait) {
return
}
const traitMenuOptions = {
title: `${this.name} Raise Trait Increase`,
defaultButton: 'Cancel',
options: {}
}
const skillSet = new Set()
for (const skill of this.actor.items.filter(i => i.type === 'skill')) {
skillSet.add(skill.name)
}
for (const item of Object.values(this.spawnMutation.embedded.Item).filter(i => i.type === 'skill')) {
skillSet.add(item.name)
}
const skillList = Array.from(skillSet)
const attrList = ['Agility', 'Smarts', 'Spirit', 'Strength', 'Vigor']
skillList.sort()
const traitMenuData = {
inputs: [
{ type: 'header', label: 'Raise! Increase an attribute' }
],
buttons: [
{ label: 'Apply', value: 'apply' },
{ label: 'Increase no traits', value: 'cancel' }
]
}
traitMenuData.inputs = traitMenuData.inputs.concat(
attrList.map((x) => { return { type: 'radio', label: x, options: ['trait', false] } }))
traitMenuData.inputs.push({ type: 'header', label: 'Increase Skills (+1 each)' })
traitMenuData.inputs = traitMenuData.inputs.concat(
skillList.map((x) => { return { type: 'radio', label: x, options: ['trait', false] } }))
const { buttons, inputs } = await warpgate.menu(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 = moduleHelpers.createEffectDocument(
this.ICON, 'Increased Trait', this.durationRounds)
effectDoc.changes = modKeys.map(key => {
return {
key, mode: foundry.CONST.ACTIVE_EFFECT_MODES.ADD, value: '+2', priority: 0
}
})
this.spawnMutation.embedded.ActiveEffect[effectDoc.name] = effectDoc
}
}
const PowerClasses = {
'arcane protection': ArcaneProtectionEffect,
'arcane-protection': ArcaneProtectionEffect,
blast: BlastEffect,
blind: BlindEffect,
bolt: BoltEffect,
'boost/lower trait': BoostLowerTraitEffect,
'boostlower-trait': BoostLowerTraitEffect,
'boost trait': BoostLowerTraitEffect,
'boost-trait': BoostLowerTraitEffect,
burrow: BurrowEffect,
burst: BurstEffect,
'conceal arcana': DetectConcealArcanaEffect,
'conceal-arcana': DetectConcealArcanaEffect,
confusion: ConfusionEffect,
darksight: DarksightEffect,
deflection: DeflectionEffect,
'detect arcana': DetectConcealArcanaEffect,
'detect-arcana': DetectConcealArcanaEffect,
'detect/conceal aracana': DetectConcealArcanaEffect,
'detectconceal-aracana': DetectConcealArcanaEffect,
disguise: DisguiseEffect,
entangle: EntangleEffect,
fear: FearEffect,
havoc: HavocEffect,
intangibility: IntangibilityEffect,
invisibility: InvisibilityEffect,
'lower trait': BoostLowerTraitEffect,
'lower-trait': BoostLowerTraitEffect,
protection: ProtectionEffect,
'shape change': ShapeChangeEffect,
'shape-change': ShapeChangeEffect,
sloth: SlothSpeedEffect,
'sloth/speed': SlothSpeedEffect,
slothspeed: SlothSpeedEffect,
smite: SmiteEffect,
speed: SlothSpeedEffect,
'spiritual-weapon': SpiritualWeaponEffect,
'summon ally': SummonAllyEffect,
'summon-ally': SummonAllyEffect,
'summon animal': SummonAnimalEffect,
'summon-animal': SummonAnimalEffect,
'summon monster': SummonMonsterEffect,
'summon-monster': SummonMonsterEffect,
"summon nature's ally": SummonNaturesAllyEffect,
'summon-natures-ally': SummonNaturesAllyEffect,
'summon planar ally': SummonPlanarAllyEffect,
'summon-planar-ally': SummonPlanarAllyEffect,
'summon undead': SummonUndeadEffect,
'summon-undead': SummonUndeadEffect,
zombie: ZombieEffect
}
export async function powerEffects (options = {}) {
const token = 'token' in options ? options.token : []
if (token === undefined || token === null) {
ui.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
}
}
ui.notifications.error(`No power effect found for ${name}`)
}
export async function shapeChangeOnDismiss (data) {
if (game.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 = game.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)
}
}
+1637
View File
@@ -0,0 +1,1637 @@
/* globals warpgate */
import { moduleName } from './globals.js'
import { PowerEffect } from './basePowers.js'
class ArcaneProtectionEffect extends PowerEffect {
get name () {
return 'Arcane Protection'
}
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,
epic: true,
effect: false
})
return mods
}
get _penaltyAmount () {
return (
(this.data.raise ? -4 : -2) + (this.data.mods.has('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.mods.has('greater')
const raise = this.data.raise
const amount = this._penaltyAmount
return `${greater ? 'Greater ' : ''}Arcane Protection (${raise ? 'major, ' : ''}${amount})`
}
}
class BanishEffect extends PowerEffect {
get name () {
return 'Banish'
}
get duration () {
return 0
}
get basePowerPoints () {
return 3
}
get usePrimaryEffect () {
return false
}
get isTargeted () {
return true
}
get isRaisable () {
return false
}
get hasAoe () {
return true
}
get menuInputs () {
const inputs = super.menuInputs
inputs.push({
type: 'select',
label: '⭐ Area of Effect',
options: [
{ html: 'None', value: 0, selected: true },
{ html: 'Small Blast Template (+1)', value: 1, selected: false },
{ html: 'Medium Blast Template (+2)', value: 2, selected: false },
{ html: 'Large Blast Template (+3)', value: 3, selected: false }
]
})
return inputs
}
get powerPoints () {
let total = super.powerPoints
total += this.data.aoe
return total
}
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
switch (this.data.aoe) {
case 0:
break
case 1:
list.push('SBT')
break
case 2:
list.push('MBT')
break
case 3:
list.push('LBT')
break
}
return list
}
async parseValues () {
await super.parseValues()
this.data.aoe = this.data.values.shift()
}
}
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({
name: 'Damage',
id: 'damage',
value: 1,
epic: false,
effect: false
})
mods.push({
name: 'Damage (immaterial trapping)',
id: 'damage',
value: 0,
epic: false,
effect: false
})
mods.push({
name: 'Deadly',
id: 'deadly',
value: 2,
epic: true,
effect: false
})
mods.push({
name: 'Hardened',
id: 'hardened',
value: 1,
epic: false,
effect: false
})
mods.push({
name: 'Shaped',
id: 'shaped',
value: 1,
epic: false,
effect: false
})
mods.push({
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.mods.has('size')) {
height *= 2
}
return `${height}" (${height * 2} yards)`
}
get _height () {
return `${this.data.mods.has('size') ? '2" (4' : '1" (2'} yards`
}
get _hardness () {
return (
(this.data.raise ? 12 : 10) + (this.data.mods.has('hardened') ? 2 : 0)
)
}
get description () {
let text = super.description
text += `<p>A barrier ${this._height} tall and ${this._length} long, of hardness ${this._hardness}. `
if (this.data.mods.has('deadly')) {
text += 'It does 2d6 damage to anyone who contacts it. '
} else if (this.data.mods.has('damage')) {
text += 'It does 2d4 damage to anyone who contacts it. '
}
if (this.data.mods.has('shaped')) {
text += 'It was shaped into a circle, square, or rectangle. '
}
text += '</p>'
return text
}
}
class BeastFriendEffect extends PowerEffect {
get name () {
return 'Beast Friend'
}
get duration () {
return (this.data.mods.has('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
mods.push(
{
name: 'Bestiarium',
value: 2,
id: 'bestiarium',
epic: true,
effect: false
},
{
name: 'Duration',
value: 1,
id: 'duration',
epic: false,
effect: false
},
{
name: 'Mind Rider',
value: 1,
id: 'mindrider',
epic: false,
effect: false
}
)
return mods
}
get menuInputs () {
const inputs = super.menuInputs
const pp = Math.max(
this.targets
.map((t) => Math.max(t.actor.system.stats.size, 1))
.reduce((a, b) => a + b, 0),
1
)
inputs.push({ type: 'number', label: 'Base power points', options: pp })
return inputs
}
async parseValues () {
await super.parseValues()
this.data.basePP = this.data.values.shift()
}
get basePowerPoints () {
return this?.data?.basePP || 2
}
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.mods.has('bestiarium')) {
text += ' The caster may even effect magical beasts.'
}
return text
}
}
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 menuInputs () {
const inputs = super.menuInputs
inputs.push({
type: 'select',
label: 'Area of Effect',
options: [
{ html: 'Small Blast Template (0)', value: 's', selected: false },
{ html: 'Medium Blast Template (0)', value: 'm', selected: true },
{ html: 'Large Blast Template (+1)', value: 'l', selected: false }
]
})
return inputs
}
get modifiers () {
const mods = super.modifiers
mods.push(
{ name: 'Damage', value: 2, id: 'damage', epic: false, effect: false },
{
name: 'Greater Blast',
value: 4,
id: 'greater',
epic: true,
effect: false
}
)
return mods
}
get powerPoints () {
let total = super.powerPoints
total += this.data.aoe === 'l' ? 1 : 0
return total
}
async parseValues () {
await super.parseValues()
this.data.aoe = this.data.values.shift()
}
get description () {
const dmgDie =
(this.data.mods.has('greater')
? 4
: this.data.mods.has('damage')
? 3
: 2) + (this.data.raise ? 1 : 0)
const size =
this.data.aoe === 'l' ? 'LBT' : this.data.aoe === 's' ? 'SBT' : 'MBT'
return (
super.description +
`
<p>The blast covers a ${size} and does ${dmgDie}d6 damage</p>`
)
}
}
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.mods.has('strong') ? '-2 ' : ''}roll as a free action.
Success removes 2 points of penalties. A raise removes the effect.</p>`
)
}
async createSecondaryEffects (maintId) {
const docs = await super.createSecondaryEffects(maintId)
if (this.data.raise) {
const strong = this.data.mods.has('strong')
const 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.duration.seconds = 594
doc.description =
this.description +
'<p>This is the raise effect which can be shaken off separately.</p>'
doc.flags[moduleName].maintId = maintId
docs.push(doc)
}
return docs
}
get modifiers () {
const mods = super.modifiers
mods.push({
name: 'Strong',
value: 1,
id: 'strong',
epic: false,
effect: false
})
return mods
}
get menuInputs () {
const inputs = super.menuInputs
inputs.push({
type: 'select',
label: 'Area of Effect',
options: [
{ html: 'None', value: 0, selected: true },
{ html: 'Medium Blast Template (+2)', value: 2, selected: false },
{ html: 'Large Blast Template (+3)', value: 3, selected: false }
]
})
return inputs
}
async parseValues () {
await super.parseValues()
this.data.aoe = this.data.values.shift()
}
get powerPoints () {
let total = super.powerPoints
total += this.data.aoe
return total
}
get effectName () {
const strong = this.data.mods.has('strong')
return `Blinded${strong ? ' (Strong)' : ''}`
}
get chatMessageEffects () {
const list = super.chatMessageEffects
switch (this.data.aoe) {
case 2:
list.push('MBT')
break
case 3:
list.push('LBT')
break
}
return list
}
}
class BoltEffect extends PowerEffect {
get name () {
return 'Bolt'
}
get icon () {
return 'icons/magic/fire/explosion-fireball-large-red-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(
{ name: 'Damage', value: 2, id: 'damage', epic: false, effect: false },
{
name: 'Disintegrate',
value: 1,
id: 'disintigrate',
epic: true,
effect: false
},
{
name: 'Greater Bolt',
value: 4,
id: 'greater',
epic: true,
effect: false
},
{ name: 'Rate of Fire', 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.mods.has('greater')
? 4
: this.data.mods.has('damage')
? 3
: 2) + (this.data.raise ? 1 : 0)
let desc = super.description + '<p>'
if (this.data.mods.has('rof')) {
desc += `Up to two bolts (RoF 2) do ${dmgDie}d6 damage each.`
} else {
desc += `The bolt does ${dmgDie}d6 damage.`
}
if (this.data.mods.has('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
}
}
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',
id: 'power',
value: 1,
epic: false,
effect: false
})
return mods
}
get effectName () {
return (
`${this.name} ${this.data.mods.has('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.mods.has('power')) {
text += '<p>Can <em>burrow</em> through solid stone, concrete, etc</p>'
}
return text
}
}
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 && this.data.direction === 'Boost') {
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.mods.has('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.createEffectDocument(this.icon, name, changes)
doc.duration.seconds = 594
doc.description =
this.description +
'<p>This is the raise effect which can be shaken off separately.</p>'
doc.flags[moduleName].maintId = maintId
docs.push(doc)
}
return docs
}
get effectName () {
let name = `${this.data.direction} ${this.data.trait.name}`
const nameMods = []
if (this.data.mods.has('greater')) {
nameMods.push('Greater')
}
if (this.data.direction === 'Lower' && this.data.mods.has('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.mods.has('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.mods.has('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 modifiers () {
const mods = super.modifiers
mods.push({
name: 'Greater Boost/Lower Trailt',
value: 2,
id: 'greater',
epic: true,
effect: false
})
mods.push({
name: 'Strong (lower only)',
value: 1,
id: 'strong',
epic: false,
effect: false
})
return mods
}
get menuInputs () {
const inputs = super.menuInputs
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())
this.data.traits = traits
inputs.push({ type: 'select', label: 'Trait', options: traitOptions })
inputs.push({ type: 'info', label: 'Boost or Lower?' })
inputs.push({ type: 'radio', label: 'Boost', options: ['isBoost', true] })
inputs.push({ type: 'radio', label: 'Lower', options: ['isBoost', false] })
return inputs
}
async parseValues () {
await super.parseValues()
this.data.trait = this.data.traits[this.data.values.shift()]
this.data.values.shift()
this.data.direction = this.data.values.shift() ? 'Boost' : 'Lower'
}
get powerPoints () {
const total = super.powerPoints
return total
}
}
class BurstEffect extends PowerEffect {
get name () {
return 'Blast'
}
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', value: 2, id: 'damage', epic: false, effect: false },
{
name: 'Greater Burst',
value: 4,
id: 'greater',
epic: true,
effect: false
}
)
return mods
}
get description () {
const dmgDie =
(this.data.mods.has('greater')
? 4
: this.data.mods.has('damage')
? 3
: 2) + (this.data.raise ? 1 : 0)
return (
super.description +
`
<p>The blast covers a Cone or Stream template and does ${dmgDie}d6 damage</p>`
)
}
}
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 menuInputs () {
const inputs = super.menuInputs
inputs.push({
type: 'select',
label: 'Area of Effect',
options: [
{ html: 'Small Blast Template (0)', value: 's', selected: false },
{ html: 'Medium Blast Template (0)', value: 'm', selected: true },
{ html: 'Large Blast Template (+1)', value: 'l', selected: false }
]
})
return inputs
}
get modifiers () {
const mods = super.modifiers
mods.push({
name: 'Greater Confusion',
value: 2,
id: 'greater',
epic: true,
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'
this.data.aoe = this.data.values.shift()
}
get description () {
const size =
this.data.aoe === 'l' ? 'LBT' : this.data.aoe === 's' ? 'SBT' : 'MBT'
let effect = 'Vulnerable'
if (this.data.raise) {
effect = 'both Distracted and Vulnerable'
} else if (this.data.distracted) {
effect = 'Distracted'
}
if (this.data.mods.has('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.distracted) {
PowerEffect.getStatus('SWADE.Vuln', 'Vulnerable', false).then((v) =>
docs.push(v)
)
}
if (this.data.mods.has('greater')) {
PowerEffect.getStatus('SWADE.Shaken', 'Shaken', false).then((v) =>
docs.push(v)
)
}
return docs
}
}
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',
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.mods.has('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
}
}
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({
name: 'Area of Effect',
value: 2,
id: 'aoe',
epic: false,
effect: false
})
mods.push({
name: 'Damage',
value: 2,
id: 'damage',
epic: false,
effect: false
})
mods.push({
name: 'Greater Damage Field',
value: 4,
id: 'greater',
epic: true,
effect: false
})
mods.push({
name: 'Mobile',
value: 2,
id: 'mobile',
epic: false,
effect: false
})
return mods
}
get description () {
let desc = super.description
let area = 'all adjacent creatures'
let damage = '2d4'
if (this.data.mods.has('greater')) {
damage = '3d6 (heavy weapon)'
} else if (this.data.mods.has('damage')) {
damage = '2d6'
}
if (this.data.mods.has('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.mods.has('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.mods.has('aoe') ? 1.5 : 0.5,
priority,
mode
},
{ key: `${base}.visibleTo`, value: [-1, 0, 1], priority, mode }
]
return changes
}
}
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',
value: 2,
id: 'greater',
epic: true,
effect: false
})
return mods
}
get description () {
let desc = super.description
desc += '<p>'
if (this.data.mods.has('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.mods.has('greater')) {
return 'Greater Darksight'
} else if (this.data.raise) {
return 'Major Darksight'
} else {
return 'Darksignt'
}
}
}
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: 'vulnerable' },
{ 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>`
)
}
}
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({
name: 'Alignment Sense (detect)',
value: 1,
id: 'alignment',
epic: false,
effect: false
})
mods.push({
name: 'Identify (detect)',
value: 1,
id: 'identify',
epic: false,
effect: false
})
mods.push({
name: 'Strong (conceal)',
value: 1,
id: 'strong',
epic: false,
effect: false
})
return mods
}
get hasAoe () {
return true
}
get menuInputs () {
const inputs = super.menuInputs
inputs.push({
type: 'select',
label: 'Area of Effect (conceal)',
options: [
{ html: 'None', value: 0, selected: true },
{ html: 'Medium Blast Template (+1)', value: 1, selected: false },
{ html: 'Large Blast Template (+2)', value: 2, selected: false }
]
})
inputs.push({ type: 'info', label: 'Detect or Conceal?' })
inputs.push({
type: 'radio',
label: 'Detect',
options: ['isDetect', true]
})
inputs.push({
type: 'radio',
label: 'Conceal',
options: ['isDetect', false]
})
return inputs
}
async parseValues () {
await super.parseValues()
this.data.aoe = this.data.values.shift()
this.data.values.shift()
this.data.detect = this.data.values.shift()
}
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.mods.has('identify')) {
desc += `<p>Items detected also give the recipient an idea of their
powers and how to activate them.</p>`
}
if (this.data.mods.has('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.mods.has('strong') ? -2 : 0) + this.data.raise ? -2 : -4} penalty.`
}
return desc
}
}
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', value: 1, id: 'size', epic: false, effect: false }
]
}
get description () {
const size = this.data.mods.has('size')
? 'of the same size as'
: 'within two sizes of'
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 : -4}
as a free action to see through the disguise.</p>`
)
}
}
const PowerClasses = {
'arcane-protection': ArcaneProtectionEffect,
banish: BanishEffect,
barrier: BarrierEffect,
'beast-friend': BeastFriendEffect,
blast: BlastEffect,
blind: BlindEffect,
bolt: BoltEffect,
'boost-lower-trait': BoostLowerTraitEffect,
'boost-trait': BoostLowerTraitEffect,
burrow: BurrowEffect,
burst: BurstEffect,
confusion: ConfusionEffect,
curse: CurseEffect,
'damage-field': DamageFieldEffect,
darksight: DarksightEffect,
deflection: DeflectionEffect,
'detect-conceal-arcana': DetectConcealArcanaEffect,
disguise: DisguiseEffect,
'lower-trait': BoostLowerTraitEffect
}
/* ---------------------------------------------------------------- */
export async function powerEffectManagementHook (effect, data, userId) {
if (game.user.id !== userId) {
return
}
const maintId = effect.getFlag(moduleName, 'maintainingId')
if (!maintId) {
return
}
const mutateOptions = {
permanent: true,
comparisonKeys: {
ActiveEffect: 'id'
}
}
const targetIds = effect.getFlag(moduleName, 'targetIds') || []
for (const targetId of targetIds) {
const mutation = {
embedded: { ActiveEffect: {} }
}
const target = canvas.tokens.get(targetId)
if (!target) {
continue
}
const effects = target.actor.effects.filter(
(e) => e.getFlag(moduleName, 'maintId') === maintId
)
for (const efct of effects) {
mutation.embedded.ActiveEffect[efct.id] = warpgate.CONST.DELETE
}
mutateOptions.description = `${effect.parent.name} is no longer ${effect.name} on ${target.name}`
await warpgate.mutate(target.document, mutation, {}, mutateOptions)
}
}
export async function powers (options = {}) {
const token = 'token' in options ? options.token : null
if (token === undefined || token === null) {
ui.notifications.error('Please select one token to be the caster')
return
}
const targets = 'targets' in options ? Array.from(options.targets) : []
const item = 'item' in options ? options.item : null
const swid = options?.name || item?.system.swid || null
if (swid in PowerClasses) {
const runner = new PowerClasses[swid](token, targets)
runner.powerEffect()
return
}
ui.notifications.error(`No power effect found for ${name}`)
}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
export async function preloadTemplates() {
const templatePaths = [
// Add paths to "modules/swade-mb-helpers/templates"
];
return loadTemplates(templatePaths);
}
+210
View File
@@ -0,0 +1,210 @@
import { log } from './globals.js'
export async function preTraitRollModifiers (actor, trait, roll, modifiers, options) {
const targets = Array.from(game.user.targets)
const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null
if (targets.some(target => target.actor.system.status.isVulnerable)) {
modifiers.push({ label: 'Target is Vulnerable', value: '+2', ignore: false })
}
if (targets.some(
target => target.actor.effects.filter(
e => !e.disabled && e.name.toLowerCase().includes('deflection')).length > 0)
) {
modifiers.push({ label: 'Target has Deflection', value: '-2', ignore: false })
}
if (targets.some(
target => target.actor.effects.filter(
e => !e.disabled && e.name.toLowerCase().includes('glow')).length > 0)
) {
modifiers.push({
label: 'Glowing target (negate 1 point of illumination penalty)',
value: '+1',
ignore: true
})
}
if (targets.some(
target => target.actor.effects.filter(
e => !e.disabled && e.name.toLowerCase().includes('shroud')).length > 0)
) {
modifiers.push({
label: 'Shrouded target',
value: '-1',
ignore: false
})
}
if (targets.length === 1 && token) {
const target = targets[0]
_addArcaneModifiers(target, modifiers)
_addRangeModifiers(token, target, options, modifiers)
const scaleMod = calcScaleMod(token, target)
if (scaleMod !== 0) {
modifiers.push({ label: 'Scale', value: scaleMod, ignore: false })
}
if (target.actor.items.find(e => e.type === 'edge' && e.system.swid === 'dodge')) {
modifiers.push({ label: 'Dodge', value: -2, ignore: true })
}
if (trait?.type === 'skill' && trait?.system?.swid === 'fighting') {
const gangUpBonus = calcGangup(token, target)
if (gangUpBonus > 0) {
modifiers.push({ label: 'Gang Up', value: gangUpBonus, ignore: false })
}
}
}
}
export async function preDamageRollModifiers (actor, item, roll, modifiers, options) {
const targets = Array.from(game.user.targets)
const token = game.canvas.tokens.controlled.length > 0 ? game.canvas.tokens.controlled[0] : null
// log('ACTOR', actor)
// log('TOKEN', token)
// log('ITEM', item)
// log('ROLL', roll)
// log('MODIFIERS', modifiers)
// log('OPTIONS', options)
// log('TARGET', targets)
if (targets.length === 1 && token) {
const target = targets[0]
_addArcaneModifiers(target, modifiers)
const weaknesses = target.actor.items.filter(
i => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-weakness'))
if (weaknesses.length > 0) {
modifiers.push(...weaknesses.map(i => { return { label: i.name, value: '+4', ignore: true } }))
}
const resistances = target.actor.items.filter(
i => i.type === 'ability' && i.system.swid.toLowerCase().includes('environmental-resistance'))
if (resistances.length > 0) {
modifiers.push(...resistances.map(i => { return { label: i.name, value: '-4', ignore: true } }))
}
if (_findItem(token.actor, 'ability', 'pack-tactics')) {
const gangupBonus = calcGangup(token, target)
if (gangupBonus > 0) {
modifiers.push({ label: 'Gang Up (Pack Tactics)', value: gangupBonus, ignore: false })
}
}
}
}
function _addRangeModifiers (token, target, options, modifiers) {
if (options?.item?.type !== 'weapon' || !options?.item?.system?.range.includes('/')) {
return
}
const ranges = options.item.system.range.split('/').map(x => parseInt(x))
const distance = getDistance(token, target)
const rollmods = CONFIG.SWADE.prototypeRollGroups.find(g => g.name === 'Range').modifiers
log('ITEM RANGES:', ranges)
if (distance <= ranges[0]) {
// nothing here
} else if (ranges.length >= 2 && distance <= ranges[1]) {
modifiers.push(rollmods[0])
} else if (ranges.length >= 3 && distance <= ranges[2]) {
modifiers.push(rollmods[1])
} else {
modifiers.push(rollmods[2]) // extreme range
}
}
function _addArcaneModifiers (target, modifiers) {
if (_findItem(target.actor, 'edge', 'improved-arcane-resistance')) {
modifiers.push({ label: 'Arcane Resistance', value: '-4', ignore: true })
} else if (_findItem(target.actor, 'edge', 'arcane-resistance')) {
modifiers.push({ label: 'Arcane Resistance', value: '-2', ignore: true })
}
const effect = target.actor.effects.find(
e => !e.disabled && e.name.toLowerCase().includes('arcane protection'))
if (effect) {
const effectName = effect.name.toLowerCase()
const effectMod = (
-2 +
(effectName.includes('major') ? -2 : 0) +
(effectName.includes('greater') ? -2 : 0)
)
modifiers.push({ label: 'Target Arcane Protection', value: effectMod, ignore: true })
}
}
function getScaleDistanceMod (token) {
const scale = token.actor.system.stats.scale
return (scale > 0 ? (scale / 2) : 0)
}
function getDistance (origin, target) {
const ray = new Ray(origin, target)
const originScale = getScaleDistanceMod(origin)
const targetScale = getScaleDistanceMod(target)
const flatDistance = game.canvas.grid.measureDistances(
[{ ray }], { gridSpaces: true })[0]
const elevation = Math.abs(origin.document.elevation - target.document.elevation)
const distance = Math.sqrt(elevation * elevation + flatDistance * flatDistance)
return distance - (originScale + targetScale)
}
function withinRange (origin, target, range) {
const distance = getDistance(origin, target)
return range >= distance
}
function _findItem (actor, type, swid) {
return actor.items.find(i => i.type === type && i.system.swid === swid)
}
function calcScaleMod (attacker, target) {
const attackerScale = attacker.actor.system.stats.scale
const targetScale = target.actor.system.stats.scale
const attackerHasSwat = !!_findItem(attacker.actor, 'ability', 'swat')
let modifier = targetScale - attackerScale
if (attackerHasSwat && modifier < 0) {
modifier = Math.min(modifier + 4, 0)
}
return modifier
}
function calcGangup (attacker, target, debug) {
debug = (typeof debug === 'undefined') ? false : debug
const range = 1.2
let modifier = 0
if (_findItem(target.actor, 'edge', 'improved-block')) {
modifier = -2
} else if (_findItem(target.actor, 'edge', 'block')) {
modifier = -1
}
const attackerHasFormationFighter = !!(_findItem(attacker.actor, 'edge', 'formation-fighter'))
const withinRangeOfToken = game.canvas.tokens.placeables.filter(t =>
t.id !== attacker.id &&
t.id !== target.id &&
t.actor.system.status.isStunned === false &&
t.visible &&
withinRange(target, t, range) &&
!(t.actor.effects.find(c => c.name === 'Incapacitated' || c.name === 'Defeated'))
)
const attackerAllies = withinRangeOfToken.filter(
t => t.document.disposition === attacker.document.disposition)
const targetAllies = withinRangeOfToken.filter(
t => t.document.disposition === target.document.disposition &&
withinRange(attacker, t, range)
)
const attackersWithFormationFighter = attackerAllies.filter(
t => !!_findItem(t.actor, 'edge', 'formation-fighter'))
const attackerCount = attackerAllies.length
const attackerFormationBonus = (
(attackerCount > 0 && attackerHasFormationFighter ? 1 : 0) +
attackersWithFormationFighter.length
)
const defenderCount = targetAllies.length
const gangUp = Math.max(
0,
Math.min(
4,
attackerCount + attackerFormationBonus - defenderCount + modifier))
if (debug) {
log('GANG UP | Attacker:', attacker)
log('GANG UP | Target:', target)
log('GANG UP | Others within range:', withinRangeOfToken)
log('GANG UP | Attacker Allies:', attackerCount)
log('GANG UP | Attacker Formation Bonus:', attackerFormationBonus)
log('GANG UP | Effective Defender Allies:', defenderCount)
log('GANG UP | Target Block Modifier:', modifier)
log('GANG UP | Total Bonus:', gangUp)
}
return gangUp
}
+7
View File
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2022 Johannes Loher
//
// SPDX-License-Identifier: MIT
export function registerSettings() {
// Register any custom module settings here
}
+65
View File
@@ -0,0 +1,65 @@
/* globals ColorAdjustmentsSamplerShader */
/* globals socketlib */
/* globals VisionMode */
/* globals warpgate */
// Import JavaScript modules
import { registerSettings } from './settings.js';
import { preloadTemplates } from './preloadTemplates.js';
import { api } from './api.js'
import { initVisionModes } from './visionModes.js'
import { requestTokenRoll } from './helpers.js'
import { preDamageRollModifiers, preTraitRollModifiers } from './rollHelpers.js'
import { shapeChangeOnDismiss } from './powerEffects.js'
import { log, moduleHelpers } from './globals.js'
import { powerEffectManagementHook } from './powers.js'
// Initialize module
Hooks.once('init', async () => {
log('Initializing swade-mb-helpers');
initVisionModes();
// Assign custom classes and constants here
// Register custom module settings
registerSettings();
// Preload Handlebars templates
await preloadTemplates();
// Register custom sheets (if any)
});
// Setup module
Hooks.once('setup', async () => {
api.registerFunctions()
// Do anything after initialization but before
// ready
});
// When ready
Hooks.once('ready', async () => {
_checkModule('warpgate')
_checkModule('socketlib')
log('Initialized SWADE MB Helpers')
warpgate.event.watch(warpgate.EVENT.DISMISS, shapeChangeOnDismiss)
});
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('swadePreRollAttribute', preTraitRollModifiers)
Hooks.on('swadePreRollSkill', preTraitRollModifiers)
Hooks.on('swadeRollDamage', preDamageRollModifiers)
Hooks.on('deleteActiveEffect', powerEffectManagementHook)
Hooks.once('socketlib.ready', () => {
const _socket = socketlib.registerModule('swade-mb-helpers')
_socket.register('requestTokenRoll', requestTokenRoll)
moduleHelpers._socket = _socket
})
+72
View File
@@ -0,0 +1,72 @@
export function initVisionModes () {
log('INIT VISION')
CONFIG.Canvas.visionModes.basic = new VisionMode({
id: 'basic',
label: 'VISION.ModeBasicVision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.85, brightness: -1.0 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0, contrast: 0, saturation: -0.85, brightness: -1.0 },
preferred: true
}
})
CONFIG.Canvas.visionModes.darkvision = new VisionMode({
id: 'darkvision',
label: 'VISION.ModeDarkvision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: 0, brightness: 0.75, tint: [0.8, 0.8, 1.0] }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED },
levels: {
[VisionMode.LIGHTING_LEVELS.DIM]: VisionMode.LIGHTING_LEVELS.BRIGHT
}
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.1, contrast: 0, saturation: 0, brightness: 0.75 },
preferred: false
}
})
CONFIG.Canvas.visionModes.lowlight = new VisionMode({
id: 'lowlight',
label: 'Low Light Vision',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.5, brightness: -0.2 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.1, contrast: 0, saturation: -0.5, brightness: -0.2 },
preferred: false
}
})
CONFIG.Canvas.visionModes.blindsense = new VisionMode({
id: 'blindsense',
label: 'Blindsense',
canvas: {
shader: ColorAdjustmentsSamplerShader,
uniforms: { contrast: 0, saturation: -0.5, brightness: -0.2 }
},
lighting: {
background: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED },
coloration: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED },
illumination: { visibility: VisionMode.LIGHTING_VISIBILITY.DISABLED }
},
vision: {
darkness: { adaptive: false },
defaults: { attenuation: 0.3, contrast: -0.5, saturation: 0.75, brightness: 0.5 },
preferred: false
}
})
}
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2024/04/21-13:43:38.456492 7f46537fe700 Delete type=3 #1
2024/04/21-13:43:38.458872 7f4651ffb700 Level-0 table #5: started
2024/04/21-13:43:38.466149 7f4651ffb700 Level-0 table #5: 17250 bytes OK
2024/04/21-13:43:38.475018 7f4651ffb700 Delete type=0 #3
2024/04/21-13:43:38.475149 7f4651ffb700 Manual compaction at level-0 from '!folders!0nDRFmMBs5DBJU9M' @ 72057594037927935 : 1 .. '!items.effects!RC1Nz6iph8wPPK1B.g9W5hJisq3MsCpZW' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2024/04/21-13:43:39.181200 7effe0fbc700 Delete type=3 #1
2024/04/21-13:43:39.184035 7efd43fff700 Level-0 table #5: started
2024/04/21-13:43:39.191796 7efd43fff700 Level-0 table #5: 6787 bytes OK
2024/04/21-13:43:39.200366 7efd43fff700 Delete type=0 #3
2024/04/21-13:43:39.200535 7efd43fff700 Manual compaction at level-0 from '!items!JWyBQe4tnOYljFAF' @ 72057594037927935 : 1 .. '!items!tWWSfEMmLmws6Yb1' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2024/04/21-13:43:40.927141 7f1577fff700 Delete type=3 #1
2024/04/21-13:43:40.929334 7f1576ffd700 Level-0 table #5: started
2024/04/21-13:43:40.933617 7f1576ffd700 Level-0 table #5: 1751 bytes OK
2024/04/21-13:43:40.939816 7f1576ffd700 Delete type=0 #3
2024/04/21-13:43:40.939947 7f1576ffd700 Manual compaction at level-0 from '!actors!U5v4gFHquo0Y1SAq' @ 72057594037927935 : 1 .. '!actors!U5v4gFHquo0Y1SAq' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2024/04/21-13:43:41.963974 7fc9667bd700 Delete type=3 #1
2024/04/21-13:43:41.966318 7fc9657bb700 Level-0 table #5: started
2024/04/21-13:43:41.972075 7fc9657bb700 Level-0 table #5: 24404 bytes OK
2024/04/21-13:43:41.975325 7fc9657bb700 Delete type=0 #3
2024/04/21-13:43:41.975428 7fc9657bb700 Manual compaction at level-0 from '!folders!8FWN37c0gcpAisAv' @ 72057594037927935 : 1 .. '!macros!ypFtlnrgQkzqLpxI' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2024/04/21-13:43:42.681666 7fdd01dcf700 Delete type=3 #1
2024/04/21-13:43:42.684152 7fdcdb7fe700 Level-0 table #5: started
2024/04/21-13:43:42.697243 7fdcdb7fe700 Level-0 table #5: 18987 bytes OK
2024/04/21-13:43:42.704337 7fdcdb7fe700 Delete type=0 #3
2024/04/21-13:43:42.704480 7fdcdb7fe700 Manual compaction at level-0 from '!journal!HbtPlHNFO1L6RVj0' @ 72057594037927935 : 1 .. '!journal.pages!w4TImRTAiNiqDWeL.vQhO6BVdKZOubTUQ' @ 0 : 0; will stop at (end)
Binary file not shown.
@@ -0,0 +1,78 @@
{
"folder": "0nDRFmMBs5DBJU9M",
"name": "Barrier",
"type": "action",
"_id": "YBfJFr0Q0Hnz3Z0Z",
"img": "icons/magic/water/barrier-ice-wall-snow.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"KBEgoqgT": {
"name": "Damage",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"rppj3jdl": {
"name": "Deadly",
"type": "damage",
"override": "2d6",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"R19q7be2": {
"name": "Lingering Damage",
"type": "damage",
"override": "2d4-2",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"nVD7h2EG": {
"name": "Lingering Deadly Damage",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
}
}
},
"swid": "barrier"
},
"effects": [],
"sort": 600000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695165795303,
"modifiedTime": 1702839170547,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!items!YBfJFr0Q0Hnz3Z0Z"
}
@@ -0,0 +1,88 @@
{
"folder": "0nDRFmMBs5DBJU9M",
"name": "Blast/Burst",
"type": "action",
"_id": "uOfqTXUyHk6M5tcp",
"img": "icons/magic/fire/explosion-fireball-large-purple-orange.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"syarnXHm": {
"name": "Damage (+2)",
"type": "damage",
"override": "3d6",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"72RI01JA": {
"name": "Greater (+4)",
"type": "damage",
"override": "4d6",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": true
},
"bJStUmyi": {
"name": "Lingering Damage",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"ETr8eRwJ": {
"name": "Lingering Extra Damage (+2)",
"type": "damage",
"override": "3d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"DvnTrhtg": {
"name": "Lingering Greater Damage (+4)",
"type": "damage",
"override": "4d4",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": true
}
}
},
"swid": "blastburst"
},
"effects": [],
"sort": 800000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695165983064,
"modifiedTime": 1702839170549,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!items!uOfqTXUyHk6M5tcp"
}
@@ -0,0 +1,110 @@
{
"name": "Bolt",
"type": "action",
"img": "icons/magic/lightning/bolt-beam-strike-blue.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"U0ReLbPz": {
"name": "Damage (+2)",
"type": "damage",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "3d6",
"isHeavyWeapon": false
},
"bgEDx2DC": {
"name": "Distegrate (+2) Resist (Vigor)",
"type": "resist",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Vigor",
"isHeavyWeapon": false
},
"rc9YFNrJ": {
"name": "Greater Bolt (+4)",
"type": "damage",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "4d6",
"isHeavyWeapon": true
},
"mL2g1HXb": {
"name": "Rate of Fire (+2)",
"type": "trait",
"dice": 2,
"resourcesUsed": null,
"modifier": "",
"override": "",
"isHeavyWeapon": false
},
"yaUtp0a1": {
"name": "Lingering Damage",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": false
},
"enYZVtNT": {
"name": "Lingering Extra Damage (+2)",
"type": "damage",
"override": "3d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"Oy39lfJP": {
"name": "Lingering Greater Damage (+4)",
"type": "damage",
"override": "4d4",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": true
}
}
},
"swid": "bolt"
},
"effects": [],
"folder": "0nDRFmMBs5DBJU9M",
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695082141993,
"modifiedTime": 1702839170544,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "8fL0gIyNIxr2hr2s",
"sort": 400000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!8fL0gIyNIxr2hr2s"
}
@@ -0,0 +1,66 @@
{
"name": "Burrow",
"type": "action",
"img": "icons/magic/earth/projectile-stone-landslide.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"hx9EGBvx": {
"name": "Surprise (Stealth)",
"type": "trait",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Stealth",
"isHeavyWeapon": false
},
"m0xSkgHw": {
"name": "Oppose Surprise (Notice)",
"type": "resist",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Notice",
"isHeavyWeapon": false
}
}
},
"swid": "burrow"
},
"effects": [],
"folder": "0nDRFmMBs5DBJU9M",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1696130722637,
"modifiedTime": 1702839170544,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "Ne3oDoPl3hc8eEX1",
"sort": 300000,
"_key": "!items!Ne3oDoPl3hc8eEX1"
}
@@ -0,0 +1,88 @@
{
"folder": "0nDRFmMBs5DBJU9M",
"name": "Damage Field",
"type": "action",
"_id": "pSztYcH5w4VNI2nb",
"img": "icons/magic/defensive/armor-shield-barrier-steel.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"vzxICJES": {
"name": "Damage (+2)",
"type": "damage",
"override": "2d6",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"B5Vbtiut": {
"name": "⭐ Greater (+4)",
"type": "damage",
"override": "3d6",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": true
},
"Ldobq3c1": {
"name": "Lingering Damage",
"type": "damage",
"override": "2d4-2",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"xQjPiDKU": {
"name": "Lingering Extra Damage (+2)",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null
},
"bLXiZ53I": {
"name": "Lingering Greater Damage",
"type": "damage",
"override": "3d4",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": true
}
}
},
"swid": "damage-field"
},
"effects": [],
"sort": 700000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695250911715,
"modifiedTime": 1702839170549,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!items!pSztYcH5w4VNI2nb"
}
@@ -0,0 +1,69 @@
{
"name": "Disarm",
"type": "action",
"img": "icons/skills/melee/sword-damaged-broken-orange.webp",
"system": {
"description": "<p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore03rules0.JournalEntryPage.03situationalr00#disarm]{DISARM}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"nRh9e5kO": {
"name": "Resist Disarm (Arm Hit)",
"type": "resist",
"override": "Strength",
"modifier": "-2",
"isHeavyWeapon": false
},
"HxIXbUus": {
"name": "Resist Disarm (Hand Hit)",
"type": "resist",
"override": "Strength",
"modifier": "-4",
"dice": null,
"resourcesUsed": null
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "SWAD MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "disarm"
},
"effects": [],
"flags": {
"core": {},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1679667342015,
"modifiedTime": 1702839170544,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "Lijthu5ONJvcIzDX",
"sort": 300000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!Lijthu5ONJvcIzDX"
}
@@ -0,0 +1,66 @@
{
"name": "Disguise",
"type": "action",
"img": "icons/skills/social/diplomacy-peace-alliance.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"m0xSkgHw": {
"name": "Detect Disguise (Notice -2)",
"type": "resist",
"override": "Notice",
"modifier": "-2",
"dice": null,
"resourcesUsed": null,
"isHeavyWeapon": false
},
"oJYREpI8": {
"name": "Detect major Disguise (Notice -4)",
"type": "resist",
"dice": null,
"resourcesUsed": null,
"modifier": "-4",
"override": "Notice",
"isHeavyWeapon": false
}
}
},
"swid": "disguise"
},
"effects": [],
"folder": "0nDRFmMBs5DBJU9M",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1696130722637,
"modifiedTime": 1702839170546,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "TteEFvR0yPWKNbI5",
"sort": 200000,
"_key": "!items!TteEFvR0yPWKNbI5"
}
@@ -0,0 +1,61 @@
{
"name": "Evade",
"type": "action",
"img": "icons/skills/movement/figure-running-gray.webp",
"system": {
"description": "<p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore03rules0.JournalEntryPage.03situationalr00#evasion]{Evasion}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"L0XtU5KM": {
"name": "Evade (Agility)",
"type": "resist",
"override": "Agility",
"modifier": "-2",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "SWADE MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "evade"
},
"effects": [],
"flags": {
"core": {},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1674019466231,
"modifiedTime": 1702839170550,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "xA7qKMmugJv7z6j1",
"sort": 900000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!xA7qKMmugJv7z6j1"
}
@@ -0,0 +1,63 @@
{
"name": "Frenzy",
"type": "action",
"img": "icons/skills/melee/strikes-sword-scimitar.webp",
"system": {
"description": "",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"qQtymA5P": {
"name": "Frenzy Attack",
"type": "trait",
"modifier": "",
"resourcesUsed": null,
"dice": 2,
"override": "Fighting",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "frenzy"
},
"effects": [],
"flags": {
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
},
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1668048955652,
"modifiedTime": 1702839170547,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "hTRU6MiIYlFUVk4T",
"sort": 700000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!hTRU6MiIYlFUVk4T"
}
@@ -0,0 +1,108 @@
{
"name": "Grapple (Tentacles)",
"type": "action",
"img": "icons/skills/melee/unarmed-punch-fist.webp",
"system": {
"description": "<p></p><p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore03rules0.JournalEntryPage.03situationalr00#grappling]{Grappling}</p><p></p><p>@UUID[Compendium.swpf-core-rules.swpf-abilities.Item.0fZ7WmUuKGgU35sJ]{Tentacles}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"XEC2IWM0": {
"name": "Grapple",
"type": "trait",
"dice": null,
"resourcesUsed": null,
"modifier": "+2",
"override": "Athletics",
"isHeavyWeapon": false
},
"g7oxsNqI": {
"name": "Resist Grapple",
"type": "resist",
"override": "Athletics",
"modifier": "",
"isHeavyWeapon": false
},
"KCvw4z5i": {
"name": "Crush",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Strength",
"isHeavyWeapon": false
},
"1wWLiKwK": {
"name": "Resist Crush",
"type": "resist",
"override": "Strength",
"modifier": "",
"isHeavyWeapon": false
},
"t1Db4fxd": {
"name": "Crush",
"type": "damage",
"modifier": "",
"override": "@str",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "grapple-tentacles"
},
"effects": [],
"flags": {
"swim": {
"config": {
"reloadSFX": "",
"fireSFX": "",
"autoFireSFX": "",
"silencedFireSFX": "",
"silencedAutoFireSFX": "",
"emptySFX": "",
"isPack": false,
"isConsumable": false,
"isSilenced": false,
"loadedAmmo": "",
"_version": 1
}
},
"core": {},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1667883835416,
"modifiedTime": 1702839170543,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "6fkUnnyILsETRfjI",
"sort": 100000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!6fkUnnyILsETRfjI"
}
@@ -0,0 +1,108 @@
{
"name": "Grapple",
"type": "action",
"img": "icons/skills/melee/unarmed-punch-fist.webp",
"system": {
"description": "<p></p><p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore03rules0.JournalEntryPage.03situationalr00#grappling]{Grappling}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"XEC2IWM0": {
"name": "Grapple",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Athletics",
"isHeavyWeapon": false
},
"g7oxsNqI": {
"name": "Resist Grapple",
"type": "resist",
"override": "Athletics",
"modifier": "",
"isHeavyWeapon": false
},
"KCvw4z5i": {
"name": "Crush",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Strength",
"isHeavyWeapon": false
},
"1wWLiKwK": {
"name": "Resist Crush",
"type": "resist",
"override": "Strength",
"modifier": "",
"isHeavyWeapon": false
},
"t1Db4fxd": {
"name": "Crush",
"type": "damage",
"modifier": "",
"override": "@str",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "grapple"
},
"effects": [],
"flags": {
"swim": {
"config": {
"reloadSFX": "",
"fireSFX": "",
"autoFireSFX": "",
"silencedFireSFX": "",
"silencedAutoFireSFX": "",
"emptySFX": "",
"isPack": false,
"isConsumable": false,
"isSilenced": false,
"loadedAmmo": "",
"_version": 1
}
},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
},
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1667883835416,
"modifiedTime": 1702839170547,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "jcWvDnoBNyVdXnsu",
"sort": 800000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!jcWvDnoBNyVdXnsu"
}
@@ -0,0 +1,94 @@
{
"name": "Havoc",
"type": "action",
"img": "icons/magic/air/air-burst-spiral-blue-gray.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"swid": "havoc",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"t1FV85tY": {
"name": "Resist minor Havoc",
"type": "resist",
"override": "Strength",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"macroActor": "default",
"isHeavyWeapon": false
},
"Rch2cQhm": {
"name": "Resist major Havoc",
"type": "resist",
"override": "Strength",
"modifier": "-2",
"dice": null,
"resourcesUsed": null,
"macroActor": "default",
"isHeavyWeapon": false
},
"BNXwvwgh": {
"name": "Strike a hard object",
"type": "damage",
"override": "2d4",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"macroActor": "default",
"isHeavyWeapon": false
},
"dOsQSsEW": {
"name": "Strike a hard object (greater)",
"type": "damage",
"override": "2d6",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"macroActor": "default",
"isHeavyWeapon": false
}
}
}
},
"effects": [],
"folder": "0nDRFmMBs5DBJU9M",
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {
"core": {},
"exportSource": {
"world": "sw-module-creation",
"system": "swade",
"coreVersion": "11.315",
"systemVersion": "3.2.2"
}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1699734652310,
"modifiedTime": 1702839170545,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "Sjg9D9FQ0uxZQrD0",
"sort": 0,
"_key": "!items!Sjg9D9FQ0uxZQrD0"
}
@@ -0,0 +1,156 @@
{
"folder": null,
"name": "Illumination",
"type": "action",
"img": "icons/magic/light/explosion-star-blue-large.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"swid": "lighting",
"additionalStats": {},
"favorite": false,
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {}
}
},
"effects": [
{
"name": "Dim Lighting",
"icon": "icons/magic/light/light-lantern-lit-white.webp",
"changes": [
{
"key": "system.stats.globalMods.trait",
"mode": 2,
"value": "-2",
"priority": null
}
],
"transfer": true,
"_id": "fpSigRcCCADmisXP",
"disabled": true,
"duration": {
"startTime": 0,
"seconds": null,
"combat": "bLRI8G4dAxvCnqbC",
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"statuses": [],
"flags": {
"swade": {
"favorite": true,
"expiration": null,
"loseTurnOnHold": false
}
},
"tint": null,
"_key": "!items.effects!RC1Nz6iph8wPPK1B.fpSigRcCCADmisXP"
},
{
"name": "Dark Lighting",
"icon": "icons/magic/light/light-candles-lit-white.webp",
"changes": [
{
"key": "system.stats.globalMods.trait",
"mode": 2,
"value": "-4",
"priority": null
}
],
"transfer": true,
"_id": "g9W5hJisq3MsCpZW",
"disabled": true,
"duration": {
"startTime": 0,
"seconds": null,
"combat": "bLRI8G4dAxvCnqbC",
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"statuses": [],
"flags": {
"swade": {
"expiration": null,
"loseTurnOnHold": false,
"favorite": true
}
},
"tint": null,
"_key": "!items.effects!RC1Nz6iph8wPPK1B.g9W5hJisq3MsCpZW"
},
{
"name": "Pitch Darkness",
"icon": "icons/magic/unholy/barrier-shield-glowing-pink.webp",
"changes": [
{
"key": "system.stats.globalMods.trait",
"mode": 2,
"value": "-6",
"priority": null
}
],
"transfer": true,
"_id": "NI4hfxplfJ5pqImL",
"disabled": true,
"duration": {
"startTime": 0,
"seconds": null,
"combat": "bLRI8G4dAxvCnqbC",
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"statuses": [],
"flags": {
"swade": {
"expiration": null,
"loseTurnOnHold": false,
"favorite": true
}
},
"tint": null,
"_key": "!items.effects!RC1Nz6iph8wPPK1B.NI4hfxplfJ5pqImL"
}
],
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1702939897892,
"modifiedTime": 1702960438222,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "RC1Nz6iph8wPPK1B",
"sort": 0,
"_key": "!items!RC1Nz6iph8wPPK1B"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,87 @@
{
"name": "Networking",
"type": "action",
"img": "icons/skills/social/diplomacy-handshake.webp",
"system": {
"description": "<p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore04theadv.JournalEntryPage.04networking0000]{Networking}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"VvEhWhmL": {
"name": "Persuasion",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Persuasion",
"isHeavyWeapon": false
},
"vyhH3l9E": {
"name": "Intimidation",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Intimidation",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "networking"
},
"effects": [],
"flags": {
"swim": {
"config": {
"reloadSFX": "",
"fireSFX": "",
"autoFireSFX": "",
"silencedFireSFX": "",
"silencedAutoFireSFX": "",
"emptySFX": "",
"isPack": false,
"isConsumable": false,
"isSilenced": false,
"loadedAmmo": "",
"_version": 1
}
},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
},
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1667885227318,
"modifiedTime": 1702839170547,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "ZlN2t7I05FjampmX",
"sort": 600000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!ZlN2t7I05FjampmX"
}
@@ -0,0 +1,19 @@
{
"name": "Oppose",
"sorting": "a",
"folder": null,
"type": "Item",
"_id": "5OjjkiKDo8RxHpGz",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.2",
"coreVersion": "11.315",
"createdTime": 1699419673111,
"modifiedTime": 1699419673111,
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
},
"_key": "!folders!5OjjkiKDo8RxHpGz"
}
@@ -0,0 +1,57 @@
{
"name": "Oppose (Spirit)",
"type": "action",
"img": "icons/magic/control/debuff-energy-hold-teal-blue.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Oppose",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"j20IwFOf": {
"name": "Oppose (Spirit)",
"type": "resist",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Spirit",
"isHeavyWeapon": false
}
}
},
"swid": "oppose-spirit"
},
"effects": [],
"folder": "5OjjkiKDo8RxHpGz",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1696130050023,
"modifiedTime": 1702839170545,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "O1bgJSYRjxmSW3R5",
"sort": -100000,
"_key": "!items!O1bgJSYRjxmSW3R5"
}
@@ -0,0 +1,59 @@
{
"name": "Power Effect",
"type": "action",
"img": "icons/magic/symbols/circled-gem-pink.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"E4Pbzvi0": {
"name": "Effect",
"type": "macro",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "",
"uuid": "Compendium.swade-mb-helpers.helper-macros.Macro.AjuA11hQ48UJNwlH",
"isHeavyWeapon": false,
"macroActor": "self"
}
}
},
"swid": "power-effect"
},
"effects": [],
"folder": null,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695081676167,
"modifiedTime": 1705985534930,
"lastModifiedBy": "Aqnn9hCDQPbyTWiq"
},
"_id": "vrfo9zsECbxan4dt",
"sort": 100000,
"_key": "!items!vrfo9zsECbxan4dt"
}
@@ -0,0 +1,19 @@
{
"name": "Power Effects",
"sorting": "a",
"folder": null,
"type": "Item",
"_id": "0nDRFmMBs5DBJU9M",
"sort": 100000,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.308",
"createdTime": 1695081947648,
"modifiedTime": 1695164535987,
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
},
"_key": "!folders!0nDRFmMBs5DBJU9M"
}
@@ -0,0 +1,108 @@
{
"name": "Push",
"type": "action",
"img": "icons/skills/melee/shield-block-bash-blue.webp",
"system": {
"description": "<p></p><p>@UUID[Compendium.swpf-core-rules.swpf-rules.swpfcore03rules0.JournalEntryPage.03situationalr00#push]{Push}</p>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"wwxI37Do": {
"name": "Push",
"type": "trait",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"override": "Strength",
"isHeavyWeapon": false
},
"o5BwRFE0": {
"name": "Oppose Push",
"type": "resist",
"override": "Strength",
"modifier": "",
"isHeavyWeapon": false
},
"SceRYgjP": {
"name": "Resist Prone",
"type": "resist",
"override": "Athletics",
"modifier": "",
"isHeavyWeapon": false
},
"JUaIJ70O": {
"name": "Running Push",
"type": "trait",
"override": "Strength",
"modifier": "+2",
"dice": null,
"resourcesUsed": null
},
"VNgJEQP8": {
"name": "Resist Prone vs Raise",
"type": "resist",
"override": "Athletics",
"modifier": "-2",
"dice": null,
"resourcesUsed": null
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "push"
},
"effects": [],
"flags": {
"swim": {
"config": {
"reloadSFX": "",
"fireSFX": "",
"autoFireSFX": "",
"silencedFireSFX": "",
"silencedAutoFireSFX": "",
"emptySFX": "",
"isPack": false,
"isConsumable": false,
"isSilenced": false,
"loadedAmmo": "",
"_version": 1
}
},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
},
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1667884673139,
"modifiedTime": 1702839170544,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "DKs4DNcTRYtcdrri",
"sort": 200000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!DKs4DNcTRYtcdrri"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"name": "Savage Pathfinder",
"sorting": "a",
"folder": null,
"type": "Item",
"_id": "rXqlP1phfPHNZrRT",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.308",
"createdTime": 1695164553594,
"modifiedTime": 1695164553594,
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
},
"_key": "!folders!rXqlP1phfPHNZrRT"
}
@@ -0,0 +1,67 @@
{
"folder": "0nDRFmMBs5DBJU9M",
"name": "Shake Off (Spirit)",
"type": "action",
"img": "icons/magic/control/debuff-energy-snare-blue.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"icoPVRIf": {
"name": "Shake Off",
"type": "resist",
"override": "Spirit",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"uuid": "",
"isHeavyWeapon": false
},
"41FetTKN": {
"name": "Shake Off (from Strong)",
"type": "resist",
"override": "Spirit",
"modifier": "-2",
"dice": null,
"resourcesUsed": null
}
}
},
"swid": "shake-off-spirit"
},
"effects": [],
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695165609050,
"modifiedTime": 1702839170545,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "Tj9Pbdh9tu3ngd03",
"sort": 750000,
"_key": "!items!Tj9Pbdh9tu3ngd03"
}
@@ -0,0 +1,65 @@
{
"name": "Shake off (Vigor)",
"type": "action",
"img": "icons/magic/control/debuff-energy-snare-brown.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"gJBLDGdP": {
"name": "Shake Off",
"type": "resist",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Vigor",
"isHeavyWeapon": false
},
"oTYF6EjQ": {
"name": "Shake Off (From Strong)",
"type": "resist",
"override": "Vigor",
"modifier": "-2",
"dice": null,
"resourcesUsed": null
}
}
},
"swid": "shake-off-vigor"
},
"effects": [],
"folder": "0nDRFmMBs5DBJU9M",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695698203871,
"modifiedTime": 1702839170546,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "XJYoBJcpr0Ar3cES",
"sort": 500000,
"_key": "!items!XJYoBJcpr0Ar3cES"
}
@@ -0,0 +1,67 @@
{
"name": "Smite",
"type": "action",
"folder": "0nDRFmMBs5DBJU9M",
"_id": "jGOCDAjMQ3uDOhvn",
"img": "icons/weapons/hammers/hammer-double-glowing-yellow.webp",
"system": {
"description": "",
"notes": "",
"source": "",
"swid": "smite",
"additionalStats": {},
"favorite": false,
"category": "Power Action",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"E4Pbzvi0": {
"name": "Effect",
"type": "macro",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "",
"uuid": "Compendium.swade-mb-helpers.helper-macros.Macro.AjuA11hQ48UJNwlH",
"macroActor": "self",
"isHeavyWeapon": false
},
"eLBgU6O6": {
"name": "Spiritual Weapon",
"type": "macro",
"override": "",
"modifier": "",
"dice": null,
"resourcesUsed": null,
"uuid": "Compendium.swade-mb-helpers.helper-macros.Macro.JzzC728WBjbE7qUP",
"macroActor": "self"
}
}
}
},
"effects": [],
"sort": 0,
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1707874199713,
"modifiedTime": 1707874294558,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!items!jGOCDAjMQ3uDOhvn"
}
@@ -0,0 +1,87 @@
{
"name": "Work the Room",
"type": "action",
"img": "icons/skills/social/diplomacy-unity-alliance.webp",
"system": {
"description": "<ul><li><p>@UUID[Compendium.swpf-core-rules.swpf-edges.H2kXu5SWIf4hZ99G]{Work the Room}</p></li><li><p>@UUID[Compendium.swpf-core-rules.swpf-edges.2W87DoEcg9sheggg]{Work the Crowd}</p></li></ul>",
"notes": "",
"additionalStats": {},
"actions": {
"dmgMod": "",
"additional": {
"VHwhhR0B": {
"name": "Persuasion",
"type": "trait",
"modifier": "",
"resourcesUsed": null,
"dice": 2,
"override": "Persuasion",
"isHeavyWeapon": false
},
"WF83NRE1": {
"name": "Performance",
"type": "trait",
"modifier": "",
"resourcesUsed": null,
"dice": 2,
"override": "Performance",
"isHeavyWeapon": false
}
},
"trait": "",
"traitMod": ""
},
"favorite": false,
"source": "Swade MB Common Actions",
"category": "Savage Pathfinder",
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"swid": "work-the-room"
},
"effects": [],
"flags": {
"swim": {
"config": {
"reloadSFX": "",
"fireSFX": "",
"autoFireSFX": "",
"silencedFireSFX": "",
"silencedAutoFireSFX": "",
"emptySFX": "",
"isPack": false,
"isConsumable": false,
"isSilenced": false,
"loadedAmmo": "",
"_version": 1
}
},
"exportSource": {
"world": "swpf-rotr",
"system": "swade",
"coreVersion": "10.291",
"systemVersion": "2.2.5"
},
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1667885448561,
"modifiedTime": 1702839170545,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"folder": "rXqlP1phfPHNZrRT",
"_id": "NhwZbABEXrt8OzT3",
"sort": 500000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!items!NhwZbABEXrt8OzT3"
}
@@ -0,0 +1,126 @@
{
"name": "Bladed Scarf",
"type": "weapon",
"img": "icons/commodities/cloth/cloth-bolt-gold-red.webp",
"system": {
"description": "<p>Knowing that seductive performances can bring out the worst in watchers, some Varisians craft rows of razor-sharp blades into their scarves. This deals Str+d4 damage to any creature successfully grappled while wielding the scarf.</p>",
"notes": "Two Hands",
"source": "Pathfinder Companion",
"additionalStats": {},
"quantity": 1,
"weight": 1,
"price": 12,
"equippable": false,
"equipStatus": 1,
"isArcaneDevice": false,
"arcaneSkillDie": {
"sides": 4,
"modifier": 0
},
"powerPoints": {},
"isVehicular": false,
"mods": 1,
"actions": {
"trait": "",
"traitMod": "",
"dmgMod": "",
"additional": {
"XEC2IWM0": {
"name": "Grapple",
"type": "trait",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Athletics",
"isHeavyWeapon": false
},
"g7oxsNqI": {
"name": "Resist Grapple",
"type": "resist",
"modifier": "",
"override": "Athletics",
"isHeavyWeapon": false
},
"KCvw4z5i": {
"name": "Crush",
"type": "trait",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "Strength",
"isHeavyWeapon": false
},
"1wWLiKwK": {
"name": "Resist Crush",
"type": "resist",
"modifier": "",
"override": "Strength",
"isHeavyWeapon": false
},
"t1Db4fxd": {
"name": "Crush",
"type": "damage",
"modifier": "",
"override": "@str",
"isHeavyWeapon": false
},
"6yiDtU88": {
"name": "Grapple Damage",
"type": "damage",
"dice": null,
"resourcesUsed": null,
"modifier": "",
"override": "@str+d4",
"isHeavyWeapon": false
}
}
},
"bonusDamageDie": 6,
"bonusDamageDice": 1,
"favorite": false,
"templates": {
"cone": false,
"stream": false,
"small": false,
"medium": false,
"large": false
},
"category": "Melee Weapon",
"grants": [],
"grantOn": 1,
"damage": "@str+d4",
"range": "",
"rof": 1,
"ap": 1,
"parry": 0,
"minStr": "d4",
"shots": 0,
"currentShots": 0,
"ammo": "",
"reloadType": "none",
"ppReloadCost": 2,
"trademark": 0,
"isHeavyWeapon": false,
"swid": "bladed-scarf"
},
"effects": [],
"folder": null,
"ownership": {
"default": 0,
"zJNNhVU4bzQxMMA9": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1696794187384,
"modifiedTime": 1702839170624,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "tWWSfEMmLmws6Yb1",
"sort": 300000,
"_key": "!items!tWWSfEMmLmws6Yb1"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,229 @@
{
"name": "Non Character Target",
"type": "npc",
"_id": "U5v4gFHquo0Y1SAq",
"img": "icons/svg/mystery-man.svg",
"system": {
"attributes": {
"agility": {
"die": {
"sides": 4,
"modifier": 0
},
"wild-die": {
"sides": 6
}
},
"smarts": {
"die": {
"sides": 4,
"modifier": 0
},
"wild-die": {
"sides": 6
},
"animal": false
},
"spirit": {
"die": {
"sides": 4,
"modifier": 0
},
"wild-die": {
"sides": 6
},
"unShakeBonus": 0
},
"strength": {
"die": {
"sides": 4,
"modifier": 0
},
"wild-die": {
"sides": 6
},
"encumbranceSteps": 0
},
"vigor": {
"die": {
"sides": 4,
"modifier": 0
},
"wild-die": {
"sides": 6
},
"unStunBonus": 0,
"soakBonus": 0,
"bleedOut": {
"modifier": 0,
"ignoreWounds": false
}
}
},
"stats": {
"speed": {
"runningDie": 6,
"runningMod": 0,
"value": 6
},
"toughness": {
"value": 0,
"armor": 0,
"modifier": 0
},
"parry": {
"value": 0,
"shield": 0,
"modifier": 0
},
"size": 0
},
"details": {
"autoCalcToughness": true,
"autoCalcParry": true,
"archetype": "",
"appearance": "",
"notes": "",
"goals": "",
"biography": {
"value": ""
},
"species": {
"name": ""
},
"currency": 0,
"wealth": {
"die": 6,
"modifier": 0,
"wild-die": 6
},
"conviction": {
"value": 0,
"active": false
}
},
"powerPoints": {},
"fatigue": {
"value": 0,
"max": 2,
"ignored": 0
},
"wounds": {
"value": 0,
"max": 0,
"ignored": 0
},
"woundsOrFatigue": {
"ignored": 0
},
"bennies": {
"value": 0,
"max": 2
},
"advances": {
"mode": "expanded",
"value": 0,
"rank": "Novice",
"details": "",
"list": []
},
"status": {
"isShaken": false,
"isDistracted": false,
"isVulnerable": false,
"isStunned": false,
"isEntangled": false,
"isBound": false,
"isIncapacitated": false
},
"initiative": {
"hasHesitant": false,
"hasLevelHeaded": false,
"hasImpLevelHeaded": false,
"hasQuick": false
},
"additionalStats": {},
"wildcard": false
},
"prototypeToken": {
"name": "Non Character Target",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "icons/svg/mystery-man.svg",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
}
},
"sight": {
"enabled": false,
"range": 0,
"angle": 360,
"visionMode": "basic",
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0
},
"detectionModes": [],
"flags": {},
"randomImg": false
},
"items": [],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1695252634314,
"modifiedTime": 1702839170585,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!actors!U5v4gFHquo0Y1SAq"
}
@@ -0,0 +1,19 @@
{
"name": "Action Deck",
"sorting": "a",
"folder": null,
"type": "Macro",
"_id": "VMi3DyJN0U0PosUH",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.3.3",
"coreVersion": "11.315",
"createdTime": 1708920283645,
"modifiedTime": 1708920283645,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!folders!VMi3DyJN0U0PosUH"
}
@@ -0,0 +1,25 @@
{
"name": "Blind",
"type": "script",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/svg/blind.svg",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Blind'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1678165762773,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "e9HvLMtaDw2qpcE8",
"folder": "hIbrWxg1nDutCSwt",
"sort": 700000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!e9HvLMtaDw2qpcE8"
}
@@ -0,0 +1,25 @@
{
"name": "Boost/Lower Trait",
"type": "script",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/life/cross-embers-glow-yellow-purple.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Boost/Lower Trait'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1677458254287,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "1AqIFHAcX5TRdE8X",
"folder": "hIbrWxg1nDutCSwt",
"sort": 2100000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!1AqIFHAcX5TRdE8X"
}
@@ -0,0 +1,27 @@
{
"name": "Burrow",
"type": "script",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/magic/earth/projectile-stone-landslide.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Burrow'\n})",
"folder": "hIbrWxg1nDutCSwt",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1696179835774,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "e4LvHlTNDy5zcGIG",
"sort": 400000,
"_key": "!macros!e4LvHlTNDy5zcGIG"
}
@@ -0,0 +1,25 @@
{
"name": "Confusion",
"type": "script",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/control/hypnosis-mesmerism-swirl.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Confusion'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1678082334572,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "L2IstecV7ivcrgUI",
"folder": "hIbrWxg1nDutCSwt",
"sort": 2000000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!L2IstecV7ivcrgUI"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
{
"name": "Darksight",
"type": "script",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/magic/perception/eye-ringed-glow-angry-small-teal.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Darksight'\n})",
"folder": "hIbrWxg1nDutCSwt",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1696179835774,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "YIdF96EfItR641oP",
"sort": 300000,
"_key": "!macros!YIdF96EfItR641oP"
}
@@ -0,0 +1,25 @@
{
"name": "Deflection",
"type": "script",
"scope": "global",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/defensive/shield-barrier-deflect-teal.webp",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Deflection'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1677645552357,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "HYTiftQW0pwwOQGH",
"folder": "hIbrWxg1nDutCSwt",
"sort": 1900000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!HYTiftQW0pwwOQGH"
}
@@ -0,0 +1,27 @@
{
"name": "Detect/Conceal Arcana",
"type": "script",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/magic/perception/third-eye-blue-red.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Detect/Conceal Arcana'\n})",
"folder": "hIbrWxg1nDutCSwt",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1696208623170,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "stHHxnYfGW0X1l5R",
"sort": 600000,
"_key": "!macros!stHHxnYfGW0X1l5R"
}
@@ -0,0 +1,27 @@
{
"name": "Disguise",
"type": "script",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/skills/social/diplomacy-peace-alliance.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Disguise'\n})",
"folder": "hIbrWxg1nDutCSwt",
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1696208623170,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "MYfrVZpLSrpp0vYW",
"sort": 200000,
"_key": "!macros!MYfrVZpLSrpp0vYW"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"name": "Effect Macros",
"sorting": "a",
"folder": null,
"type": "Macro",
"_id": "hIbrWxg1nDutCSwt",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.2",
"coreVersion": "11.315",
"createdTime": 1695251622720,
"modifiedTime": 1700436446749,
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
},
"_key": "!folders!hIbrWxg1nDutCSwt"
}
@@ -0,0 +1,25 @@
{
"name": "Entangle",
"type": "script",
"scope": "global",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/nature/root-vine-barrier-wall-brown.webp",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Entangle'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1678164427219,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "2TOeRNCJT3T2px8D",
"folder": "hIbrWxg1nDutCSwt",
"sort": 1800000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!2TOeRNCJT3T2px8D"
}
@@ -0,0 +1,25 @@
{
"name": "Fear Table",
"type": "script",
"_id": "S6HY6RqjPTt0z0yY",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/magic/death/undead-ghost-scream-teal.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.fearTable(actor)",
"folder": null,
"sort": 200000,
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1703096862424,
"modifiedTime": 1707686618258,
"lastModifiedBy": "Aqnn9hCDQPbyTWiq"
},
"_key": "!macros!S6HY6RqjPTt0z0yY"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
{
"name": "Havoc",
"type": "script",
"scope": "global",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/air/air-burst-spiral-yellow.webp",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Havoc'\n})",
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.2",
"coreVersion": "11.315",
"createdTime": 1678164427219,
"modifiedTime": 1700436564699,
"lastModifiedBy": "R9ZgY0IvWl8ovIuT"
},
"folder": "hIbrWxg1nDutCSwt",
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_id": "0CalvjuxtMvY2enn",
"sort": 0,
"_key": "!macros!0CalvjuxtMvY2enn"
}
@@ -0,0 +1,25 @@
{
"name": "Intangibility",
"type": "script",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/magic/control/debuff-energy-hold-levitate-blue-yellow.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Intangibility'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1678168528898,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "OMDjgWLJyE9BJAwT",
"folder": "hIbrWxg1nDutCSwt",
"sort": 1700000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!OMDjgWLJyE9BJAwT"
}
@@ -0,0 +1,25 @@
{
"name": "Invisibility",
"type": "script",
"author": "R9ZgY0IvWl8ovIuT",
"img": "icons/svg/invisible.svg",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Invisibility'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1678168163811,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "11GOryzx2Q8MXbT6",
"folder": "hIbrWxg1nDutCSwt",
"sort": 1600000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!11GOryzx2Q8MXbT6"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"name": "Macro Manager",
"sorting": "a",
"folder": null,
"type": "Macro",
"_id": "8FWN37c0gcpAisAv",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.3.3",
"coreVersion": "11.315",
"createdTime": 1708920245023,
"modifiedTime": 1708920245023,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!folders!8FWN37c0gcpAisAv"
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
{
"name": "Poison",
"sorting": "a",
"folder": null,
"type": "Macro",
"_id": "DbVwJ0fIfuijv8Nu",
"sort": 0,
"color": null,
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1705990230579,
"modifiedTime": 1705990230579,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_key": "!folders!DbVwJ0fIfuijv8Nu"
}
@@ -0,0 +1,27 @@
{
"name": "Power Effects Item Action",
"type": "script",
"author": "sVoCvBU1knmXzoYe",
"img": "icons/magic/symbols/rune-sigil-black-pink.webp",
"scope": "global",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n item,\n targets: game.user.targets,\n})",
"folder": null,
"ownership": {
"default": 0,
"sVoCvBU1knmXzoYe": 3
},
"flags": {
"core": {}
},
"_stats": {
"systemId": "swade",
"systemVersion": "3.2.5",
"coreVersion": "11.315",
"createdTime": 1693853383361,
"modifiedTime": 1707686618258,
"lastModifiedBy": "Aqnn9hCDQPbyTWiq"
},
"_id": "AjuA11hQ48UJNwlH",
"sort": 900000,
"_key": "!macros!AjuA11hQ48UJNwlH"
}
@@ -0,0 +1,25 @@
{
"name": "Protection",
"type": "script",
"scope": "global",
"author": "R9ZgY0IvWl8ovIuT",
"img": "systems/swade/assets/icons/status/status_protection.svg",
"command": "game.modules.get('swade-mb-helpers').api.powerEffects({\n token,\n targets: game.user.targets,\n name: 'Protection'\n})",
"flags": {},
"_stats": {
"systemId": "swade",
"systemVersion": "3.1.4",
"coreVersion": "11.311",
"createdTime": 1677630174987,
"modifiedTime": 1696209757148,
"lastModifiedBy": "sVoCvBU1knmXzoYe"
},
"_id": "YETsNWOWfIxyLPdC",
"folder": "hIbrWxg1nDutCSwt",
"sort": 1500000,
"ownership": {
"default": 0,
"R9ZgY0IvWl8ovIuT": 3
},
"_key": "!macros!YETsNWOWfIxyLPdC"
}

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