several changes that were not properly split up
- Macro: Request fear check specialization macro
- Macro: Fear Table to call the new fearTable api endpoint
- API: rulesVersion property
- API: fearTable(actor) calls the relevant premium core rules module's fear
table
- API: added requestFearRollFromTokens special helper
- Trait roll hooks for:
- Glow/Shroud
- Range modifiers
- added a summary chat message for the roll results to requested rolls.
- added a target number option to requested rolls.
This commit is contained in:
+6
-3
@@ -1,5 +1,5 @@
|
||||
import { log } from './shim.js'
|
||||
import { requestRollFromTokens } from './helpers.js'
|
||||
import { log, shim } from './shim.js'
|
||||
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
|
||||
import { powerEffects } from './powerEffects.js'
|
||||
|
||||
export class api {
|
||||
@@ -11,8 +11,11 @@ export class api {
|
||||
static globals () {
|
||||
const moduleName = 'swade-mb-helpers'
|
||||
game.modules.get(moduleName).api = {
|
||||
rulesVersion: shim.rulesVersion,
|
||||
fearTable: shim.fearTableHelper,
|
||||
powerEffects,
|
||||
requestRollFromTokens
|
||||
requestRollFromTokens,
|
||||
requestFearRollFromTokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+62
-1
@@ -1,5 +1,25 @@
|
||||
import { shim } from './shim.js'
|
||||
|
||||
export async function requestFearRollFromTokens (tokens, options = {}) {
|
||||
// tokens: list of tokens to request the roll from
|
||||
// options:
|
||||
// title: tile for the roll dialog. Will have "- {{ token name }}" appended
|
||||
// flavour: flavor text for the roll card. Defaults to title
|
||||
// fear: value of the fear modifier. Defaults to 0. Positive number.
|
||||
const requestingUser = shim.user
|
||||
const title = options?.title || `${requestingUser.name} requests a Fear check`
|
||||
const flavour = options?.flavour || options?.flavor || title
|
||||
const fear = options.fear || 0
|
||||
const rollOpts = {
|
||||
title,
|
||||
flavour,
|
||||
mods: [
|
||||
{ label: 'Fear Penalty', value: Math.abs(fear) * -1, ignore: false }
|
||||
]
|
||||
}
|
||||
return requestRollFromTokens(tokens, 'attribute', 'spirit', rollOpts)
|
||||
}
|
||||
|
||||
export async function requestRollFromTokens (tokens, rollType, rollDesc, options = {}) {
|
||||
// tokens: list of tokens to request a roll from
|
||||
// rollType: 'attribute' or 'skill
|
||||
@@ -8,17 +28,20 @@ export async function requestRollFromTokens (tokens, rollType, rollDesc, options
|
||||
// title: title for the roll dialog. Will have "- {{ token name }}"
|
||||
// appended
|
||||
// flavour: flavor text for the roll card. Defaults to title
|
||||
// targetNumber: defaults to 4
|
||||
// mods: list of modifiers {label: "", value: 0, ignore: false}
|
||||
// modCallback: callback function that takes a token and returns a list of
|
||||
// modifiers in the same format as modifiers, above
|
||||
const requestingUser = shim.user
|
||||
const title = options?.title || `${requestingUser.name} requests a ${rollDesc} roll`
|
||||
const flavour = options?.flavour || options?.flavor || title
|
||||
const targetNumber = options?.targetNumber || 4
|
||||
const promises = []
|
||||
for (const token of tokens) {
|
||||
const owner = shim.warpgateUtil.firstOwner(token.document)
|
||||
const rollOpts = {
|
||||
title: `${title} - ${token.name}`,
|
||||
targetNumber,
|
||||
flavour
|
||||
}
|
||||
const additionalMods = []
|
||||
@@ -39,7 +62,45 @@ export async function requestRollFromTokens (tokens, rollType, rollDesc, options
|
||||
promises.push(shim.socket.executeAsUser(requestTokenRoll, owner.id,
|
||||
token.scene.id, token.id, rollType, rollDesc, rollOpts))
|
||||
}
|
||||
const results = await Promise.allSettled(promises)
|
||||
const results = (await Promise.allSettled(promises)).map(r => r.value)
|
||||
const contentExtra = targetNumber === 4 ? '' : ` vs TN: ${targetNumber}`
|
||||
const messageData = {
|
||||
flavor: flavour,
|
||||
speaker: { alias: 'Requested Roll Results' },
|
||||
whisper: [...shim.ChatMessage.getWhisperRecipients('GM'), requestingUser],
|
||||
content: `<p>Results of ${rollDesc[0].toUpperCase()}${rollDesc.slice(1)} roll${contentExtra}:</p>
|
||||
<table><thead><tr><th>Token</th><th>Roll</th><th>Result</th></tr></thead><tbody>`,
|
||||
rolls: []
|
||||
}
|
||||
for (const result of results) {
|
||||
const token = shim.game.scenes.get(result.sceneId).tokens.get(result.tokenId)
|
||||
const roll = (
|
||||
result.result instanceof shim.CONFIG.Dice.SwadeRoll
|
||||
? result.result
|
||||
: shim.CONFIG.Dice[result.result.class].fromData(result.result)
|
||||
)
|
||||
roll.targetNumber = targetNumber
|
||||
let textResult = ''
|
||||
if (roll.successes === -1) {
|
||||
textResult = 'CRITICAL FAILURE'
|
||||
} else if (roll.successes === 0) {
|
||||
textResult = 'failed'
|
||||
} else if (roll.successes === 1) {
|
||||
textResult = 'success'
|
||||
} else {
|
||||
textResult = `success and ${roll.successes - 1} raise${roll.successes > 2 ? 's' : ''}`
|
||||
}
|
||||
messageData.content += ('<tr>' +
|
||||
`<th>${token.name}</th>` +
|
||||
`<td>${roll ? roll.total : '<i>Canceled</i>'}</td>` +
|
||||
`<td>${textResult}</td>` +
|
||||
'</tr>')
|
||||
if (roll) {
|
||||
messageData.rolls.unshift(roll)
|
||||
}
|
||||
}
|
||||
messageData.content += '</tbody></table>'
|
||||
shim.ChatMessage.create(messageData, {})
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ Hooks.on('init', () => {
|
||||
vision: {
|
||||
darkness: { adaptive: false },
|
||||
defaults: { attenuation: 0.1, contrast: 0, saturation: 0, brightness: 0.75 },
|
||||
preferred: true
|
||||
preferred: false
|
||||
}
|
||||
})
|
||||
CONFIG.Canvas.visionModes.lowlight = new VisionMode({
|
||||
@@ -67,7 +67,7 @@ Hooks.on('init', () => {
|
||||
vision: {
|
||||
darkness: { adaptive: false },
|
||||
defaults: { attenuation: 0.1, contrast: 0, saturation: -0.5, brightness: -0.2 },
|
||||
preferred: true
|
||||
preferred: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
+30
-1
@@ -1,5 +1,5 @@
|
||||
import { CONST, log, shim } from './shim.js'
|
||||
import { requestRollFromTokens } from './helpers.js'
|
||||
import { requestFearRollFromTokens, requestRollFromTokens } from './helpers.js'
|
||||
|
||||
class PowerEffect {
|
||||
constructor (token, targets) {
|
||||
@@ -546,6 +546,34 @@ class EntangleEffect extends TargetedPowerEffect {
|
||||
}
|
||||
}
|
||||
|
||||
class FearEffect extends TargetedPowerEffect {
|
||||
get name () {
|
||||
return 'Fear'
|
||||
}
|
||||
|
||||
get baseDurationRounds () {
|
||||
return 1
|
||||
}
|
||||
|
||||
async prepResult () {
|
||||
this.raise = (this.buttons === 'raise')
|
||||
}
|
||||
|
||||
async applyResult () {
|
||||
await super.applyResult()
|
||||
await shim.wait(1000)
|
||||
const options = {
|
||||
title: 'Fear check!',
|
||||
flavor: 'Failure: roll on the Fear Table if wildcard, Panicked if extra',
|
||||
mods: []
|
||||
}
|
||||
if (this.raise) {
|
||||
options.fear = '-2'
|
||||
}
|
||||
await requestFearRollFromTokens(this.targets, options)
|
||||
}
|
||||
}
|
||||
|
||||
class HavocEffect extends TargetedPowerEffect {
|
||||
get name () {
|
||||
return 'Havoc'
|
||||
@@ -1555,6 +1583,7 @@ const PowerClasses = {
|
||||
'detectconceal-aracana': DetectConcealArcanaEffect,
|
||||
disguise: DisguiseEffect,
|
||||
entangle: EntangleEffect,
|
||||
fear: FearEffect,
|
||||
havoc: HavocEffect,
|
||||
intangibility: IntangibilityEffect,
|
||||
invisibility: InvisibilityEffect,
|
||||
|
||||
+53
-4
@@ -19,9 +19,30 @@ export async function preTraitRollModifiers (actor, trait, roll, modifiers, opti
|
||||
) {
|
||||
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 })
|
||||
@@ -67,6 +88,25 @@ export async function preDamageRollModifiers (actor, item, roll, modifiers, opti
|
||||
}
|
||||
}
|
||||
|
||||
function _addRangeModifiers (token, target, options, modifiers) {
|
||||
if (options?.item?.type !== 'weapon' || !options?.item?.system?.range.includes('/')) {
|
||||
return
|
||||
}
|
||||
const ranges = options.item.system.range.split('/').map(x => parseInt(x))
|
||||
const distance = getDistance(token, target)
|
||||
const rollmods = shim.CONFIG.SWADE.prototypeRollGroups.find(g => g.name === 'Range').modifiers
|
||||
log('ITEM RANGES:', ranges)
|
||||
if (distance <= ranges[0]) {
|
||||
// nothing here
|
||||
} else if (ranges.length >= 2 && distance <= ranges[1]) {
|
||||
modifiers.push(rollmods[0])
|
||||
} else if (ranges.length >= 3 && distance <= ranges[2]) {
|
||||
modifiers.push(rollmods[1])
|
||||
} else {
|
||||
modifiers.push(rollmods[2]) // extreme range
|
||||
}
|
||||
}
|
||||
|
||||
function _addArcaneModifiers (target, modifiers) {
|
||||
if (_findItem(target.actor, 'edge', 'improved-arcane-resistance')) {
|
||||
modifiers.push({ label: 'Arcane Resistance', value: '-4', ignore: true })
|
||||
@@ -86,12 +126,21 @@ function _addArcaneModifiers (target, modifiers) {
|
||||
}
|
||||
}
|
||||
|
||||
function withinRange (origin, target, range) {
|
||||
function getScaleDistanceMod (token) {
|
||||
const scale = token.actor.system.stats.scale
|
||||
return (scale > 0 ? (scale / 2) : 0)
|
||||
}
|
||||
|
||||
function getDistance (origin, target) {
|
||||
const ray = new Ray(origin, target)
|
||||
const originScale = getScaleDistanceMod(origin)
|
||||
const targetScale = getScaleDistanceMod(target)
|
||||
const distance = shim.canvas.grid.measureDistances([{ ray }], { gridSpaces: true })[0]
|
||||
const originScale = origin.actor.system.stats.scale
|
||||
const targetScale = target.actor.system.stats.scale
|
||||
range += (originScale > 0 ? originScale / 2 : 0) + (targetScale > 0 ? targetScale / 2 : 0)
|
||||
return distance - (originScale + targetScale)
|
||||
}
|
||||
|
||||
function withinRange (origin, target, range) {
|
||||
const distance = getDistance(origin, target)
|
||||
return range >= distance
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,22 @@ export class shim {
|
||||
return ActiveEffect
|
||||
}
|
||||
|
||||
static get CONFIG () {
|
||||
return CONFIG
|
||||
}
|
||||
|
||||
static get Actor () {
|
||||
return Actor
|
||||
}
|
||||
|
||||
static get ChatMessage () {
|
||||
return ChatMessage
|
||||
}
|
||||
|
||||
static get game () {
|
||||
return game
|
||||
}
|
||||
|
||||
static get canvas () {
|
||||
return game.canvas
|
||||
}
|
||||
@@ -63,6 +75,16 @@ export class shim {
|
||||
return shim._socket
|
||||
}
|
||||
|
||||
static get rulesVersion () {
|
||||
if (game.modules.get('swpf-core-rules')?.active) {
|
||||
return 'swpf'
|
||||
}
|
||||
if (game.modules.get('swade-core-rules')?.active) {
|
||||
return 'swade'
|
||||
}
|
||||
return 'system'
|
||||
}
|
||||
|
||||
static mergeObject (...args) {
|
||||
return mergeObject(...args)
|
||||
}
|
||||
@@ -136,6 +158,14 @@ export class shim {
|
||||
return warpgate.util
|
||||
}
|
||||
|
||||
static get fearTableHelper () {
|
||||
switch (shim.rulesVersion) {
|
||||
case 'swade': return coreFearDialog
|
||||
case 'swpf': return swpfFearDialog
|
||||
}
|
||||
throw new ReferenceError('No premium module active. No fear table found')
|
||||
}
|
||||
|
||||
static getActorFolderByPath (path) {
|
||||
const names = path.split('/')
|
||||
if (names[0] === '') {
|
||||
|
||||
Reference in New Issue
Block a user