Cast A Spell

You might also like

Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1of 6

/*

Welcome to the Caster's Spellbook.


This macro was designed to facilitate casting spells from a
character with a large list of spells, like a dual class double caster
build, or a caster with multiple spellcasting entries.
This macro will sort by the spellcasting entries, available spell levels,
and finally the spells you have at those levels
Left clicking on the spell expends the slot/uses/focus point and
posts the spell to the Chat Log.
Right clicking on the spell pops open the spell edit sheet same as if you had
edited
the spell from within your character sheet.
*/

let tokens = [];


if(canvas.tokens.ownedTokens.find(x=>x.actor === game.user.character))
tokens.push(canvas.tokens.ownedTokens.find(x=>x.actor === game.user.character));
if (tokens.length < 1){
if (canvas.tokens.controlled.length < 1) { return ui.notifications.warn('No token
is selected.'); }
if (canvas.tokens.controlled.length > 1) { return ui.notifications.warn('Only 1
token should be selected'); }
tokens.push(canvas.tokens.controlled[0]);
}

token = tokens[0];

if (!token.actor.isSpellcaster) { return ui.notifications.warn(`${token.actor.name}


is not a Spellcaster`); }

const script = async function Spells(id, tokenID){


for (const token of [canvas.tokens.ownedTokens.find(x=>x.id === tokenID)]) {
let spells = [];
let buttons = {};
const spellData = await
(token.actor.itemTypes.spellcastingEntry.find( i => i.id === id)).getSpellData();
spellData.levels.forEach(sp => {
if(!spellData.isRitual && !spellData.isPrepared && !
spellData.isInnate && !spellData.isFocusPool && !spellData.isFlexible && !
sp.isCantrip && sp.uses.value < 1) { return; }
if (sp.uses?.value !== undefined &&
sp.uses?.value === 0 ) { return; }
sp.active.forEach((spa,index) => {
if(spa === null) { return; }
if(spa.expended) { return; }
if(spa.spell.isFocusSpell && !
spa.spell.isCantrip && token.actor.system.resources.focus.value === 0) { return; }
let type = '';
if (spellData.isRitual) { type = 'ritual'}
spells.push({name: spa.spell.name, spell: spa,
lvl: sp.level, type: type, index: index, sEId: spellData.id});
});
});

spells.sort((a, b) => {
if (a.lvl === b.lvl)
return a.name
.toUpperCase()
.localeCompare(b.name.toUpperCase(), undefined, {
sensitivity: "base",
});
return a.lvl - b.lvl;
});

if(spells.length === 0) { return ui.notifications.info("You have


no spells available or are out of focus points"); }

await Levels();
/* Dialog box */
async function quickDialog({data, title = `Quick Dialog`} = {}) {
data = data instanceof Array ? data : [data];

return await new Promise(async (resolve) => {


let content = `
<table style="width:100%">
${data.map(({type, label, options}, i)=> {
if(type.toLowerCase() === `select`) {
return `<tr><th style="width:50%"><label>${label}</label></th><td
style="width:50%"><select style="font-size:12px" id="${i}qd">${options.map((e,i)=>
`<option value="${e}">${e}</option>`).join(``)}</td></tr>`;
}
else if(type.toLowerCase() === `checkbox`){
return `<tr><th style="width:50%"><label>${label}</label></th><td
style="width:50%"><input type="${type}" id="${i}qd" ${options || ``}/></td></tr>`;
}
else{
return `<tr><th style="width:50%"><label>${label}</label></th><td
style="width:50%"><input type="${type}" id="${i}qd" value="${options instanceof
Array ? options[0] : options}"/></td></tr>`;
}
}).join(``)}
</table>`;

await new Dialog({


title, content,
buttons : {
Ok : { label : `Ok`, callback : (html) => {
resolve(Array(data.length).fill().map((e,i)=>{
let {type} = data[i];
if(type.toLowerCase() === `select`){
return html.find(`select#${i}qd`).val();
}
else{
switch(type.toLowerCase()){
case `text` :
case `password` :
case `radio` :
return html.find(`input#${i}qd`)[0].value;
case `checkbox` :
return html.find(`input#${i}qd`)[0].checked;
case `number` :
return html.find(`input#${i}qd`)[0].valueAsNumber;
}
}
}));
}}
},
default : 'Ok'
},{width:"auto"})._render(true);
document.getElementById("0qd").focus();
});
}

async function Levels() {


buttons = {};
let levels = [...new Set(spells.map(l => l.lvl))];
levels.forEach((index,value)=> {
if (index === 0) { index = 'Cantrip'}
async function Filter(){
if (index === 'Cantrip') { spells = spells.filter(c
=> c.spell.spell.isCantrip); }
else{ spells = spells.filter(l => l.lvl === index); }
}
buttons[value] = {label: index, callback: async () =>
{ await Filter(); await Spell(); }}
});
await Diag({title: "Spell Level?", buttons});
}

async function Spell() {


buttons = {};
spells.forEach((value,index) => {
async function Consume(){
const s_entry =
token.actor.itemTypes.spellcastingEntry.find(e => e.id === value.sEId);

console.log(value);

if(value.spell.spell.overlays.size > 0){


let spell_variants =
Array.from(value.spell.spell.overlays).map(ovr => ({name: ovr.name??
value.spell.spell.name, id: ovr._id, lvl:value.lvl}));

spell_variants.sort((a, b) => {
if (a.lvl === b.lvl)
return a.name
.toUpperCase()
.localeCompare(b.name.toUpperCase(), undefined, {
sensitivity: "base",
});
return a.lvl - b.lvl;
});

/* Build dialog data */


const ovr_data = [
{ label : `Choose a Spell Variant:`, type : `select`, options
: spell_variants.map(p=> p.name) }
];

/* Query user for variant choice */


const variant_choice = await quickDialog({data : ovr_data,
title : `Variants Detected`});

/* Obtain the ID of the chosen variant, then use that ID to


fetch the modified spell */
const vrId = spell_variants.find(x => x.name ===
variant_choice[0]).id;
let variant = value.spell.spell.loadVariant({castLevel:
value.lvl, overlayIds:[vrId]});

/* Overwrite the chosen spell's damage formula */


value.spell.spell = variant;
}

if(!s_entry.isFlexible || value.spell.spell.isFocusSpell ||
value.spell.spell.isCantrip) await s_entry.cast(value.spell.spell,{slot:
value.index,level: value.lvl,message: true});
else {
await value.spell.spell.toMessage(event, {data:
{castLevel:value.lvl}});

await
token.actor.update({"system.attributes.sp.value":Math.max(0,token.actor.system.attr
ibutes.sp.value - value.lvl)});
}

new Sequence()

.sound(`https://media.discordapp.net/attachments/546409000644640776/899016148698415
154/Dimenssional_Strike.wav`)
.volume(0.5)
.play();

console.log(value);
console.log(s_entry);

if (game.user.targets.size > 0
&& value.spell.spell.system.spellType.value === "attack")

{
let es_data = [
{ label : `Choose what to target`, type :
`select`, options : ["Armor Class", "Reflex DC", "Fortitude DC", "Will DC"] },
{ label : `MAP`, type : `select`, options :["No
MAP", "MAP-5", "MAP-10"] },
{ label : `# of Rolls`, type: `select`, options:
["1", "2", "3"] }
];

const target = Array.from(game.user.targets)[0];

let es_choice = await quickDialog({data: es_data,


title: `Cast ${value.spell.name}`})

let DC = target.actor.system.attributes.ac.value;

if(es_choice[0] === "Reflex DC") DC =


target.actor.system.saves.reflex.dc;
if(es_choice[0] === "Fortitude DC") DC =
target.actor.system.saves.fortitude.dc;
if(es_choice[0] === "Will DC") DC =
target.actor.system.saves.will.dc;

let map = 1;
if(es_choice[1] === "MAP-5") map = 2;
if(es_choice[1] === "MAP-10") map = 3;

let rolls = parseInt(es_choice[2]);

for(let i = 0; i < rolls; i++){


await value.spell.spell.rollAttack(event,map,
{ dc:{ value:DC }, callback: async (data) => {
console.log(data);
if(data.options.degreeOfSuccess > 1) {
const castingMod =
s_entry.system.ability.value;
let applyMod = false;

for(const [key, d_entry] of


Object.entries(value.spell.spell.system.damage.value)){
console.log(d_entry);
if(d_entry.applyMod) applyMod = true;

}
const rollData =
value.spell.spell.getRollData({castLevel: value.level});
const dmg =
value.spell.spell.getDamageFormula((value.spell.isCantrip?
Math.ceil(token.actor.level/2):value.level),rollData);
const roll = new Roll(`${dmg}`);
await roll.toMessage({flavor:`${value.name} -
Damage`});
}
}});
}
}
};
buttons[index] = {label: value.name, value:
value.spell.spell ,callback: async () => { await Consume(); }}
});
await Diag({title: "Pick a Spell to Cast", buttons});
spells.forEach( async s => {
const elements = await document.getElementsByClassName("dialog-button");
let myElem1 = [...document.getElementsByClassName("app window-app
dialog")].pop();
myElem1.style.display = "flex";
myElem1.style.flexWrap = "wrap";
myElem1.style.height = "auto";
myElem1.style.width = "200px";
myElem1.style.gap = "5px 5px";
let myElem2 = [...document.getElementsByClassName("dialog-buttons")].pop();
myElem2.style.display = "flex";
myElem2.style.flexFlow = "column wrap";
let element;
for (var i = 0; i < elements.length; i++) {
if (elements[i].innerText === s.name) {
element = elements[i];

element.style.lineHeight = "normal";
await $(element).bind("contextmenu", function
() {
s.spell.spell.sheet.render(true);
});
}
}
});
};
}
async function Diag({title,buttons,content} = {}) {
await new Promise((resolve) => {
new Dialog({
title,
buttons,
}).render(true);
setTimeout(resolve,10);
});
}
}

let content = `
<style>
.psya-buttons {
margin: 0 auto;
}

.psya-buttons:hover {
background-color:#44c767;
}
</style>
<div><strong>Choose a Spellcasting Entry:</strong></div><script>${script}
</script>`;
token.actor.itemTypes.spellcastingEntry.forEach((value,index) => {
const test = value.getSpellData();
if (test.isFocusPool && !test.levels.some(x => x.isCantrip) &&
token.actor.system.resources.focus.value === 0){ return; }
content = content + `<button name="button${index}" class="psya-buttons ${index}"
type="button" value="${value.name}" onclick="Spells('${value.id}','$
{tokens[0].id}')">${value.name}</button>`
});

await new Promise(async (resolve) => {


setTimeout(resolve,200);
await new Dialog({
title:"Spellbook",
content,
buttons:{ Close: { label: "Close" } },
},{width: 210}).render(true);
});

let myElem = [...document.getElementsByClassName("app window-app dialog")].pop();


if (myElem.style === undefined) { myElem = [...document.getElementsByClassName("app
window-app dialog")].pop(); }
myElem.style.resize = "both";
myElem.style.overflow = "auto";

You might also like