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:
2023-12-23 22:50:15 -06:00
parent c43fafa7df
commit 6ed989c4bc
44 changed files with 514 additions and 95 deletions
+6 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+30
View File
@@ -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] === '') {