class CompanionManager extends FormApplication {
|
|
constructor(actor) {
|
|
super();
|
|
this.actor = actor;
|
|
}
|
|
|
|
static get defaultOptions() {
|
|
return {
|
|
...super.defaultOptions,
|
|
title: game.i18n.localize("AE.dialogs.companionManager.title"),
|
|
id: "companionManager",
|
|
template: `modules/automated-evocations/templates/companionmanager.hbs`,
|
|
resizable: true,
|
|
width: 300,
|
|
height: window.innerHeight > 400 ? 400 : window.innerHeight - 100,
|
|
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
|
};
|
|
}
|
|
|
|
static get api() {
|
|
return {
|
|
dnd5e: {
|
|
getSummonInfo(args, spellLevel) {
|
|
const spellDC = (args[0].assignedActor?.system.attributes.spelldc) || 0;
|
|
return {
|
|
level: (args[0].spellLevel || spellLevel) - spellLevel,
|
|
maxHP: args[0].assignedActor?.system.attributes.hp.max || 1,
|
|
modifier: args[0].assignedActor?.system.abilities[args[0].assignedActor?.system.attributes.spellcasting]?.mod,
|
|
dc: spellDC,
|
|
attack: {
|
|
ms: spellDC - 8 + args[0].assignedActor?.system.bonuses.msak.attack,
|
|
rs: spellDC - 8 + args[0].assignedActor?.system.bonuses.rsak.attack,
|
|
mw: args[0].assignedActor?.system.bonuses.mwak.attack,
|
|
rw: args[0].assignedActor?.system.bonuses.rwak.attack,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
getData() {
|
|
return {};
|
|
}
|
|
|
|
async activateListeners(html) {
|
|
html.find("#companion-list").before(`<div class="searchbox"><input type="text" class="searchinput" placeholder="Drag and Drop an actor to add it to the list."></div>`)
|
|
this.loadCompanions();
|
|
html.on("input", ".searchinput", this._onSearch.bind(this));
|
|
html.on("click", "#remove-companion", this._onRemoveCompanion.bind(this));
|
|
html.on("click", "#summon-companion", this._onSummonCompanion.bind(this));
|
|
html.on("click", ".actor-name", this._onOpenSheet.bind(this));
|
|
html.on("dragstart", "#companion", async (event) => {
|
|
event.originalEvent.dataTransfer.setData(
|
|
"text/plain",
|
|
event.currentTarget.dataset.elid
|
|
);
|
|
});
|
|
html.on("dragend", "#companion", async (event) => {
|
|
event.originalEvent.dataTransfer.setData(
|
|
"text/plain",
|
|
event.currentTarget.dataset.elid
|
|
);
|
|
});
|
|
}
|
|
|
|
_onSearch(event) {
|
|
const search = $(event.currentTarget).val();
|
|
this.element.find(".actor-name").each(function() {
|
|
if ($(this).text().toLowerCase().includes(search.toLowerCase())) {
|
|
$(this).parent().slideDown(200);
|
|
} else {
|
|
$(this).parent().slideUp(200);
|
|
}
|
|
});
|
|
}
|
|
|
|
async _onDrop(event) {
|
|
let data;
|
|
try {
|
|
data = JSON.parse(event.dataTransfer.getData("text/plain"));
|
|
} catch {
|
|
data = event.dataTransfer.getData("text/plain");
|
|
}
|
|
const li = this.element.find(`[data-elid="${data}"]`);
|
|
if (li.length && !$(event.target).hasClass("nodrop")) {
|
|
let target = $(event.target).closest("li");
|
|
if (target.length && target[0].dataset.elid != data) {
|
|
$(li).remove();
|
|
target.before($(li));
|
|
}
|
|
}
|
|
if (!data.type === "Actor") return;
|
|
const actor = await fromUuid(data.uuid)
|
|
this.element
|
|
.find("#companion-list")
|
|
.append(this.generateLi({ id: actor.id }));
|
|
this.saveData();
|
|
}
|
|
|
|
async _onSummonCompanion(event) {
|
|
this.minimize();
|
|
const animation = $(event.currentTarget.parentElement.parentElement)
|
|
.find(".anim-dropdown")
|
|
.val();
|
|
const aId = event.currentTarget.dataset.aid;
|
|
const actor = game.actors.get(aId);
|
|
const duplicates = $(event.currentTarget.parentElement.parentElement)
|
|
.find("#companion-number-val")
|
|
.val();
|
|
const tokenData = await actor.getTokenData({elevation: _token?.data?.elevation ?? 0});
|
|
const posData = await warpgate.crosshairs.show({
|
|
size: Math.max(Math.max(tokenData.width,tokenData.height)*(tokenData.texture.scaleX + tokenData.texture.scaleY)/2, 0.5),
|
|
icon: "modules/automated-evocations/assets/black-hole-bolas.webp",
|
|
label: "",
|
|
});
|
|
if (!posData || posData.cancelled) {
|
|
this.maximize();
|
|
return;
|
|
}
|
|
if (typeof AECONSTS.animationFunctions[animation].fn == "string") {
|
|
this.evaluateExpression(game.macros.getName(AECONSTS.animationFunctions[animation].fn).command, posData, tokenData);
|
|
|
|
}else{
|
|
AECONSTS.animationFunctions[animation].fn(posData, tokenData);
|
|
}
|
|
|
|
await this.wait(AECONSTS.animationFunctions[animation].time);
|
|
//get custom data macro
|
|
const customTokenData = await this.evaluateExpression(game.macros.getName(`AE_Companion_Macro(${actor.name})`)?.command, {summon: actor,spellLevel: this.spellLevel || 0, duplicates: duplicates, assignedActor: this.caster || game.user.character || _token.actor}) || {};
|
|
customTokenData.elevation = posData?.flags?.levels?.elevation ?? _token?.document?.elevation ?? 0;
|
|
customTokenData.elevation = parseFloat(customTokenData.elevation);
|
|
tokenData.elevation = customTokenData.elevation;
|
|
Hooks.on("preCreateToken", (tokenDoc, td) => {
|
|
td ??= {};
|
|
td.elevation = customTokenData.elevation;
|
|
tokenDoc.updateSource({elevation: customTokenData.elevation});
|
|
});
|
|
warpgate.spawnAt(
|
|
{ x: posData.x, y: posData.y},
|
|
tokenData,
|
|
customTokenData || {},
|
|
{},
|
|
{ duplicates }
|
|
);
|
|
console.log("Automated Evocations Summoning:", {
|
|
assignedActor: this.caster || game?.user?.character || _token?.actor,
|
|
spellLevel: this.spellLevel || 0,
|
|
duplicates: duplicates,
|
|
warpgateData: customTokenData || {},
|
|
summon: actor,
|
|
tokenData: tokenData,
|
|
posData: posData,
|
|
})
|
|
if(game.settings.get(AECONSTS.MN, "autoclose")) this.close();
|
|
else this.maximize();
|
|
}
|
|
|
|
async evaluateExpression(expression, ...args) {
|
|
if (!expression) return null;
|
|
const AsyncFunction = (async function () {}).constructor;
|
|
const fn = new AsyncFunction("args" ,$("<span />", { html: expression }).text());
|
|
try {
|
|
return await fn(args);
|
|
} catch(e) {
|
|
ui.notifications.error("There was an error in your macro syntax. See the console (F12) for details");
|
|
console.error(e);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
async _onRemoveCompanion(event) {
|
|
Dialog.confirm({
|
|
title: game.i18n.localize("AE.dialogs.companionManager.confirm.title"),
|
|
content: game.i18n.localize(
|
|
"AE.dialogs.companionManager.confirm.content"
|
|
),
|
|
yes: () => {
|
|
event.currentTarget.parentElement.remove();
|
|
this.saveData();
|
|
},
|
|
no: () => {},
|
|
defaultYes: false,
|
|
});
|
|
}
|
|
|
|
async _onOpenSheet(event) {
|
|
const actorId = event.currentTarget.parentElement.dataset.aid;
|
|
const actor = game.actors.get(actorId);
|
|
if (actor) {
|
|
actor.sheet.render(true);
|
|
}
|
|
}
|
|
|
|
async loadCompanions() {
|
|
let data = this.actor && (this.actor.getFlag(AECONSTS.MN,"isLocal") || game.settings.get(AECONSTS.MN, "storeonactor")) ? this.actor.getFlag(AECONSTS.MN,"companions") || [] : game.user.getFlag(AECONSTS.MN, "companions");
|
|
if (data) {
|
|
for (let companion of data) {
|
|
this.element.find("#companion-list").append(this.generateLi(companion));
|
|
}
|
|
}
|
|
}
|
|
|
|
generateLi(data) {
|
|
const actor = game.actors.get(data.id) || game.actors.getName(data.id);
|
|
if (!actor) return "";
|
|
const restricted = game.settings.get(AECONSTS.MN, "restrictOwned")
|
|
if(restricted && !actor.isOwner) return "";
|
|
let $li = $(`
|
|
<li id="companion" class="companion-item" data-aid="${
|
|
actor.id
|
|
}" data-elid="${randomID()}" draggable="true">
|
|
<div class="summon-btn">
|
|
<img class="actor-image" src="${actor.img}" alt="">
|
|
<div class="warpgate-btn" id="summon-companion" data-aid="${actor.id}"></div>
|
|
</div>
|
|
<span class="actor-name">${actor.name}</span>
|
|
<div class="companion-number"><input type="number" min="1" max="99" class="fancy-input" step="1" id="companion-number-val" value="${
|
|
data.number || 1
|
|
}"></div>
|
|
<select class="anim-dropdown">
|
|
${this.getAnimations(data.animation)}
|
|
</select>
|
|
<i id="remove-companion" class="fas fa-trash"></i>
|
|
</li>
|
|
`);
|
|
// <i id="advanced-params" class="fas fa-edit"></i>
|
|
return $li;
|
|
}
|
|
|
|
getAnimations(anim) {
|
|
|
|
let animList = "";
|
|
for (let [group, animations] of Object.entries(AECONSTS.animations)) {
|
|
const localGroup = game.i18n.localize(`AE.groups.${group}`)
|
|
animList+=`<optgroup label="${localGroup == `AE.groups.${group}` ? group : localGroup}">`;
|
|
for (let a of animations) {
|
|
animList += `<option value="${a.key}" ${
|
|
a.key == anim ? "selected" : ""
|
|
}>${a.name}</option>`;
|
|
}
|
|
animList += "</optgroup>";
|
|
}
|
|
return animList;
|
|
}
|
|
async wait(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async saveData() {
|
|
let data = [];
|
|
for (let companion of this.element.find(".companion-item")) {
|
|
data.push({
|
|
id: companion.dataset.aid,
|
|
animation: $(companion).find(".anim-dropdown").val(),
|
|
number: $(companion).find("#companion-number-val").val(),
|
|
});
|
|
}
|
|
this.actor && (this.actor.getFlag(AECONSTS.MN,"isLocal") || game.settings.get(AECONSTS.MN, "storeonactor")) ? this.actor.setFlag(AECONSTS.MN,"companions", data) : game.user.setFlag(AECONSTS.MN, "companions", data);
|
|
}
|
|
|
|
close(noSave = false) {
|
|
if (!noSave) this.saveData();
|
|
super.close();
|
|
}
|
|
|
|
}
|
|
|
|
class SimpleCompanionManager extends CompanionManager {
|
|
constructor(summonData,spellLevel,actor) {
|
|
super();
|
|
this.caster = actor;
|
|
this.summons = summonData;
|
|
this.spellLevel = spellLevel
|
|
}
|
|
|
|
async activateListeners(html) {
|
|
for (let summon of this.summons) {
|
|
this.element.find("#companion-list").append(this.generateLi(summon));
|
|
}
|
|
|
|
html.on("click", "#summon-companion", this._onSummonCompanion.bind(this));
|
|
html.on("click", ".actor-name", this._onOpenSheet.bind(this));
|
|
}
|
|
|
|
_onDrop(event) {}
|
|
|
|
close() {
|
|
super.close(true);
|
|
}
|
|
}
|