229 lines
8.2 KiB
JavaScript
229 lines
8.2 KiB
JavaScript
import { BoLRoll } from "../controllers/bol-rolls.js";
|
|
|
|
/**
|
|
* BoL Macro API — accessible via game.bol.macros
|
|
*
|
|
* Usage examples (in a Foundry macro):
|
|
*
|
|
* game.bol.macros.rollAttribute("vigor")
|
|
* game.bol.macros.rollAttribute("mind")
|
|
*
|
|
* game.bol.macros.rollAptitude("melee")
|
|
* game.bol.macros.rollAptitude("ranged")
|
|
* game.bol.macros.rollAptitude("def")
|
|
* game.bol.macros.rollAptitude("init")
|
|
*
|
|
* game.bol.macros.rollWeapon("Épée courte") // by name (partial match)
|
|
* game.bol.macros.rollWeapon(0) // by index (first weapon)
|
|
*
|
|
* game.bol.macros.rollSpell("Boule de feu") // by name (partial match)
|
|
* game.bol.macros.rollSpell(0) // by index
|
|
*
|
|
* game.bol.macros.rollAlchemy("Potion de soin") // by name (partial match)
|
|
* game.bol.macros.rollAlchemy(0) // by index
|
|
*
|
|
* game.bol.macros.rollHoroscope("minor")
|
|
* game.bol.macros.rollHoroscope("major")
|
|
*
|
|
* // Generic dispatcher:
|
|
* game.bol.macros.roll("attribute", "vigor")
|
|
* game.bol.macros.roll("aptitude", "melee")
|
|
* game.bol.macros.roll("weapon", "Épée courte")
|
|
* game.bol.macros.roll("spell", "Boule de feu")
|
|
* game.bol.macros.roll("alchemy", 0)
|
|
* game.bol.macros.roll("horoscope", "minor")
|
|
*/
|
|
export class Macros {
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Resolves the actor for macro use:
|
|
* - If multiple tokens are selected → error (always)
|
|
* - If exactly one token is selected → use it (GM or player)
|
|
* - If no token selected and user is GM → error (GM must select a token)
|
|
* - If no token selected and user is a player → use their assigned character
|
|
* @returns {Actor|null}
|
|
*/
|
|
static getSpeakersActor() {
|
|
const tokens = canvas.tokens?.controlled ?? []
|
|
|
|
if (tokens.length > 1) {
|
|
ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected'))
|
|
return null
|
|
}
|
|
|
|
if (tokens.length === 1) {
|
|
return tokens[0].actor ?? null
|
|
}
|
|
|
|
// No token selected
|
|
if (game.user.isGM) {
|
|
ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
|
|
return null
|
|
}
|
|
|
|
// Player: fall back to their assigned character
|
|
const actor = game.user.character
|
|
if (!actor) {
|
|
ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
|
|
return null
|
|
}
|
|
return actor
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Finds an item on an actor by name (partial, case-insensitive) or index.
|
|
* @param {Actor} actor
|
|
* @param {string} type - item type: "weapon", "spell", "alchemy"
|
|
* @param {string|number} nameOrIndex
|
|
* @returns {object|undefined}
|
|
*/
|
|
static _findItem(actor, type, nameOrIndex) {
|
|
const items = actor.items.filter(i => i.type === type)
|
|
if (items.length === 0) {
|
|
ui.notifications.warn(`${actor.name} : aucun(e) ${type} trouvé(e).`)
|
|
return undefined
|
|
}
|
|
if (nameOrIndex === undefined || nameOrIndex === null) {
|
|
if (items.length === 1) return foundry.utils.duplicate(items[0])
|
|
const names = items.map((it, i) => `[${i}] ${it.name}`).join(', ')
|
|
ui.notifications.warn(`Précisez le nom ou l'index : ${names}`)
|
|
return undefined
|
|
}
|
|
if (typeof nameOrIndex === "number") {
|
|
const item = items[nameOrIndex]
|
|
if (!item) {
|
|
ui.notifications.warn(`${actor.name} : index ${nameOrIndex} invalide pour ${type}.`)
|
|
return undefined
|
|
}
|
|
return foundry.utils.duplicate(item)
|
|
}
|
|
const lower = String(nameOrIndex).toLowerCase()
|
|
const found = items.find(i => i.name.toLowerCase().includes(lower))
|
|
if (!found) {
|
|
ui.notifications.warn(`${actor.name} : ${type} "${nameOrIndex}" introuvable.`)
|
|
return undefined
|
|
}
|
|
return foundry.utils.duplicate(found)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll an attribute check.
|
|
* @param {string} key - "vigor" | "agility" | "mind" | "appeal"
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollAttribute(key = "vigor", actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
if (!actor.system.attributes[key]) {
|
|
ui.notifications.warn(`Attribut inconnu : "${key}". Valeurs : vigor, agility, mind, appeal`)
|
|
return
|
|
}
|
|
return BoLRoll.attributeCheck(actor, key)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll an aptitude check.
|
|
* @param {string} key - "init" | "melee" | "ranged" | "def"
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollAptitude(key = "melee", actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
if (!actor.system.aptitudes[key]) {
|
|
ui.notifications.warn(`Aptitude inconnue : "${key}". Valeurs : init, melee, ranged, def`)
|
|
return
|
|
}
|
|
return BoLRoll.aptitudeCheck(actor, key)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll a weapon attack.
|
|
* @param {string|number} [nameOrIndex] - weapon name (partial) or index. Defaults to first weapon if only one.
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollWeapon(nameOrIndex = undefined, actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
const weapon = this._findItem(actor, "weapon", nameOrIndex)
|
|
if (!weapon) return
|
|
return BoLRoll.weaponCheckWithWeapon(actor, weapon)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll a spell check.
|
|
* @param {string|number} [nameOrIndex] - spell name (partial) or index
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollSpell(nameOrIndex = undefined, actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
if ((actor.system.resources.power?.value ?? 1) <= 0) {
|
|
ui.notifications.warn("Plus assez de points de Pouvoir !")
|
|
return
|
|
}
|
|
const spell = this._findItem(actor, "spell", nameOrIndex)
|
|
if (!spell) return
|
|
return BoLRoll.spellCheckWithSpell(actor, spell)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll an alchemy check.
|
|
* @param {string|number} [nameOrIndex] - alchemy item name (partial) or index
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollAlchemy(nameOrIndex = undefined, actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
const alchemy = this._findItem(actor, "alchemy", nameOrIndex)
|
|
if (!alchemy) return
|
|
return BoLRoll.alchemyCheckWithItem(actor, alchemy)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Roll a horoscope check.
|
|
* @param {"minor"|"major"} [type="minor"]
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static rollHoroscope(type = "minor", actor = undefined) {
|
|
actor = actor ?? this.getSpeakersActor()
|
|
if (!actor) return
|
|
return BoLRoll.horoscopeCheck(actor, undefined, type)
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/**
|
|
* Generic roll dispatcher.
|
|
* @param {"attribute"|"aptitude"|"weapon"|"spell"|"alchemy"|"horoscope"} type
|
|
* @param {string|number} [key] - attribute/aptitude key, item name/index, or horoscope type
|
|
* @param {Actor} [actor] - optional, defaults to selected/owned token
|
|
*/
|
|
static roll(type, key = undefined, actor = undefined) {
|
|
switch (type) {
|
|
case "attribute": return this.rollAttribute(key ?? "vigor", actor)
|
|
case "aptitude": return this.rollAptitude(key ?? "melee", actor)
|
|
case "weapon": return this.rollWeapon(key, actor)
|
|
case "spell": return this.rollSpell(key, actor)
|
|
case "alchemy": return this.rollAlchemy(key, actor)
|
|
case "horoscope": return this.rollHoroscope(key ?? "minor", actor)
|
|
default:
|
|
ui.notifications.warn(`Type de jet inconnu : "${type}". Types valides : attribute, aptitude, weapon, spell, alchemy, horoscope`)
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
// Kept for backward-compat (previously called by old macros)
|
|
static rollMacro = async function (rollType, key) {
|
|
const actor = Macros.getSpeakersActor()
|
|
if (!actor) return
|
|
return Macros.roll(rollType, key, actor)
|
|
}
|
|
}
|