Open Stendarpaval opened 4 years ago
Hi Stendarpaval,
I've been using Turn Alert to track rounds/level spells. I would love to use the calendar to track longer duration spells. If you have macros that are working, would you please share them with me?
@Motara7 Yeah, not a problem. Here’s the macro I use for spell tracking:
//
// This macro requires the About Time module to work.
//
const durationLengths = ["1 minute", "10 minutes", "1 hour", "8 hours","24 hours"];
const durationNum = [1, 10, 60, 480, 1440];
const text = '<div style="width:100%; text-align:center;">' + "Select spell duration:" + '</div>';
if (!actor) {
ui.notifications.warn('You need to select a token before using this macro!');
} else {
let buttons = {}, dialog, content = text;
durationLengths.forEach((str)=> {
buttons[str] = {
label : str,
callback : () => {
const targetTime = game.Gametime.DTNow();
targetTime.minutes += durationNum[durationLengths.indexOf(str)];
sendChatMsg("In " + str + ", this spell will end!", str);
const endedMsg = msgFormat(actor.getActiveTokens()[0].name + "'s spell ended", "The spell that was cast " + str + " ago has ended by now.", str);
const spellDurationId = game.Gametime.reminderAt(targetTime, endedMsg);
actor.setFlag("dnd5e","spellDurationId",spellDurationId);
console.log("Spell Tracker: added spell id", spellDurationId, "at", game.Gametime.getTimeString(),"(in-game time)");
dialog.render(true);
}
}
});
dialog = new Dialog({title : 'Set spell duration', content, buttons}).render(true);
};
function msgFormat(isActiveMsg, msgContent, durationText) {
const htmlMsg = '<div><div class="dnd5e chat-card item-card">\
<header class="card-header flexrow red-header">\
<img src="icons/tools/navigation/hourglass-yellow.webp" title="Spell Tracker" width="36" height="36">\
<h3 class="item-name">' + isActiveMsg + '</h3>\
</header>\
<div class="card-content br-text" style="display: block;">\
<p>' + msgContent + '</p></div>\
<footer class="card-footer">\
<span>Spell Duration Tracker</span>\
<span>' + durationText + '</span>\
<span>' + actor.name + '\
</footer>\
</div>';
return htmlMsg;
}
function sendChatMsg(msgContent, durationText) {
const chatData = {
user: game.user.id,
speaker: game.user,
content: msgFormat(actor.getActiveTokens()[0].name + " is casting a Spell", msgContent, durationText),
};
ChatMessage.create(chatData,{});
};
If you add the toolCreateFromSeconds
function I outlined in my original comment, then you can use this following macro to see an overview of tracked spells (with the date and time at which they end, plus the name of the token that was selected when you started tracking it):
var names = [];
var spellQueue = [];
var trackerIDs = [];
for (let i = 0; i < game.Gametime.ElapsedTime._eventQueue.size; i++) {
const message = game.Gametime.ElapsedTime._eventQueue.array[i]._args[0];
// var names = [];
if (message != undefined) {
const nameStart = message.lastIndexOf("</span> <span>") + 35;
const nameEnd = message.lastIndexOf(" </footer> </div>");
const name = message.slice(nameStart,nameEnd);
names[i] = name;
// console.log(name);
//console.log(message);
var spellSeconds = game.Gametime.ElapsedTime._eventQueue.array[i]._time;
var spellDate = game.Gametime.ElapsedTime.toolCreateFromSeconds(spellSeconds);
// console.log(spellDate.shortDate());
spellQueue[i] = spellDate.shortDate().date + " " + spellDate.shortDate().time + ": " + name;
trackerIDs[i] = game.Gametime.ElapsedTime._eventQueue.array[i]._uid;
};
};
// console.log(spellQueue);
// console.log(trackerIDs);
let buttons = {}, dialog, content = `<div sytle="width:100%; text-align:left;></div>`;
spellQueue.forEach((str)=> {
buttons[str] = {
label : str,
callback : () => {
var i = spellQueue.indexOf(str);
game.Gametime.clearTimeout(trackerIDs[i]);
sendChatMsg(names[i] + "'s spell ended early.")
delete buttons[str];
dialog.render(true);
}
}
});
dialog = new Dialog({title : 'Spelltracker Queue', content, buttons}).render(true);
// const info = dialog.appId;
// console.log(info);
//console.log(spellQueue);
//game.Gametime.clearTimeout(1603284372311);
//console.log(game.Gametime.queue());
function sendChatMsg(msgContent) {
const chatData = {
user: game.user.id,
speaker: speaker,
content: msgContent
};
ChatMessage.create(chatData,{});
}
There’s a small bug when a tracked spell ends after you shut down and restart the server, which causes the “Spell Ended“ chat message to appear multiple times, but I haven’t found a way or the time to solve it yet. Edit: code block formatting
Thank you so much for your reply. I am having problems. I suspect my ignorance is getting in the way. If I understand correctly, I need to make a script macro out of the first part:
`// // This macro requires the About Time module to work. //
const durationLengths = ["1 minute", "10 minutes", "1 hour", "8 hours","24 hours"]; const durationNum = [1, 10, 60, 480, 1440]; const text = ' ' + "Select spell duration:" + ' ';
if (!actor) { ui.notifications.warn('You need to select a token before using this macro!'); } else {
let buttons = {}, dialog, content = text;
durationLengths.forEach((str)=> { buttons[str] = { label : str, callback : () => { const targetTime = game.Gametime.DTNow(); targetTime.minutes += durationNum[durationLengths.indexOf(str)]; sendChatMsg("In " + str + ", this spell will end!", str); const endedMsg = msgFormat(actor.getActiveTokens()[0].name + "'s spell ended", "The spell that was cast " + str + " ago has ended by now.", str); const spellDurationId = game.Gametime.reminderAt(targetTime, endedMsg); actor.setFlag("dnd5e","spellDurationId",spellDurationId); console.log("Spell Tracker: added spell id", spellDurationId, "at", game.Gametime.getTimeString(),"(in-game time)"); dialog.render(true); } } });
dialog = new Dialog({title : 'Set spell duration', content, buttons}).render(true); };
function msgFormat(isActiveMsg, msgContent, durationText) { const htmlMsg = '
http://icons/tools/navigation/hourglass-yellow.webp ' + isActiveMsg + '
' + msgContent + '
Spell Duration Tracker ' + durationText + ' ' + actor.name + '
'; return htmlMsg; }
function sendChatMsg(msgContent, durationText) { const chatData = { user: game.user.id, speaker: game.user, content: msgFormat(actor.getActiveTokens()[0].name + " is casting a Spell", msgContent, durationText), }; ChatMessage.create(chatData,{}); };` I have created the macro and selected an actor, however, when I execute the macro nothing happens. By which I mean nothing. I get no error message on screen or in the console. There are two things I see which may or may not be causing a problem:
On Wed, Nov 25, 2020 at 1:22 AM Stendarpaval notifications@github.com wrote:
@Motara7 https://github.com/Motara7 Yeah, not a problem. Here’s the macro I use for spell tracking:
`// // This macro requires the About Time module to work. //
const durationLengths = ["1 minute", "10 minutes", "1 hour", "8 hours","24 hours"]; const durationNum = [1, 10, 60, 480, 1440]; const text = ' ' + "Select spell duration:" + ' ';
if (!actor) { ui.notifications.warn('You need to select a token before using this macro!'); } else {
let buttons = {}, dialog, content = text;
durationLengths.forEach((str)=> { buttons[str] = { label : str, callback : () => { const targetTime = game.Gametime.DTNow(); targetTime.minutes += durationNum[durationLengths.indexOf(str)]; sendChatMsg("In " + str + ", this spell will end!", str); const endedMsg = msgFormat(actor.getActiveTokens()[0].name + "'s spell ended", "The spell that was cast " + str + " ago has ended by now.", str); const spellDurationId = game.Gametime.reminderAt(targetTime, endedMsg); actor.setFlag("dnd5e","spellDurationId",spellDurationId); console.log("Spell Tracker: added spell id", spellDurationId, "at", game.Gametime.getTimeString(),"(in-game time)"); dialog.render(true); } } });
dialog = new Dialog({title : 'Set spell duration', content, buttons}).render(true); };
function msgFormat(isActiveMsg, msgContent, durationText) { const htmlMsg = '
http://icons/tools/navigation/hourglass-yellow.webp ' + isActiveMsg + '
' + msgContent + '
Spell Duration Tracker ' + durationText + ' ' + actor.name + '
'; return htmlMsg; }
function sendChatMsg(msgContent, durationText) { const chatData = { user: game.user.id, speaker: game.user, content: msgFormat(actor.getActiveTokens()[0].name + " is casting a Spell", msgContent, durationText), }; ChatMessage.create(chatData,{}); };`
If you add the toolCreateFromSeconds function I outlined in my original comment, then you can use this following macro to see an overview of tracked spells (with the date and time at which they end, plus the name of the token that was selected when you started tracking it):
`var names = []; var spellQueue = []; var trackerIDs = [];
for (let i = 0; i < game.Gametime.ElapsedTime._eventQueue.size; i++) { const message = game.Gametime.ElapsedTime._eventQueue.array[i]._args[0]; // var names = []; if (message != undefined) { const nameStart = message.lastIndexOf(" ") + 35; const nameEnd = message.lastIndexOf(" "); const name = message.slice(nameStart,nameEnd); names[i] = name; // console.log(name); //console.log(message); var spellSeconds = game.Gametime.ElapsedTime._eventQueue.array[i]._time; var spellDate = game.Gametime.ElapsedTime.toolCreateFromSeconds(spellSeconds); // console.log(spellDate.shortDate()); spellQueue[i] = spellDate.shortDate().date + " " + spellDate.shortDate().time + ": " + name; trackerIDs[i] = game.Gametime.ElapsedTime._eventQueue.array[i]._uid; }; }; // console.log(spellQueue); // console.log(trackerIDs);
let buttons = {}, dialog, content = <div sytle="width:100%; text-align:left;>
spellQueue.forEach((str)=> { buttons[str] = { label : str, callback : () => { var i = spellQueue.indexOf(str); game.Gametime.clearTimeout(trackerIDs[i]); sendChatMsg(names[i] + "'s spell ended early.") delete buttons[str]; dialog.render(true); } } });
dialog = new Dialog({title : 'Spelltracker Queue', content, buttons}).render(true); // const info = dialog.appId; // console.log(info); //console.log(spellQueue); //game.Gametime.clearTimeout(1603284372311); //console.log(game.Gametime.queue());
function sendChatMsg(msgContent) { const chatData = { user: game.user.id, speaker: speaker, content: msgContent }; ChatMessage.create(chatData,{}); }`
There’s a small bug when a tracked spell ends after you shut down and restart the server, which causes the “Spell Ended“ chat message to appear multiple times, but I haven’t found a way or the time to solve it yet.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/DasSauerkraut/calendar-weather/issues/155#issuecomment-733578390, or unsubscribe https://github.com/notifications/unsubscribe-auth/ARX2HTHVJCM7WSP6L7ZTMOLSRTEDXANCNFSM4S2E6MWQ .
@Motara7 Sorry for the late reply, I don't check github very often. I'm also sorry to hear that it's not working for you. The way you pasted my code makes it seem like you weren't able to copy it with the same formatting as it was presented originally. In other words, perhaps some lines ended up being broken off too soon, which might cause some issues.
Thus I've attached two files with the code, which you should be able to open in any text editor and copy it from there without any risk of losing its formatting.
spellEndedNotification.txt spelltracker_queue.txt
As for your numbered questions:
One reason why the second macro may not have worked for you is because you didn't add the function I outlined in my original post to ElapsedTime.js (which I realized some time ago is actually part of the About Time module, not really the calendar module). Without the toolCreateFromSeconds()
function, the second macro shouldn't be able to display dates and time.
I hope that helps.
Thank you again for your response. spellEndedNotification is working as expected. You are correct. I must have copied the code poorly and I didn't realize it was 2 macros!
I am going to tinker with it to see if I can get it to multiply the selected spell duration (for minutes, tens-of-minutes, and hours) by the caster level of the selected token and I'm going to try to add a dialog box to add in the name of the spell/effect. If you like, I will send you the script if I get it to work.
What does spelltracker_queue do? I can't make heads or tails of the code and when I run the macro I get the following error:
Macros.js:224 TypeError: game.Gametime.ElapsedTime.toolCreateFromSeconds is not a function at Macro.eval (eval at callScriptMacroFunction (Macros.js:176),
@Motara7 Glad to hear that spellEndedNotification
worked for you! Please keep me posted if you manage to add a dialog box to add the name of the spell/effect, because that's something I'm definitely interested in.
spelltracker_queue
is a macro that allows you to cancel the tracking of a given spell. This can be really handy when there are multiple spells active and one character fails a concentration check and stops concentrating on a spell.
Activating the macro should create a dialog box with a list of all currently tracked spells, along with the name of the character that was selected at the time as well as the date and the time at which the spell is scheduled to end. I'll try to remember to make a screenshot of it later and share it with you.
The reason you currently get that error is because in order for the spelltracker_queue
macro to work, you need to edit a javascript file inside the About Time module. That is actually the reason I created this issue in the first place, and I also mentioned this in my previous comment.
Somewhere in the first 60 lines you should find the following code snippet:
static currentTime(): DTMod {
return DTMod.fromSeconds(PseudoClock.currentTime);
}
static currentTimeSeconds() : number {
return PseudoClock.currentTime % DTCalc.spd;
}
You should edit the above snippet to look like this: (basically, add a new function called toolCreateFromSeconds()
to the ElapsedTime class)
static currentTime(): DTMod {
return DTMod.fromSeconds(PseudoClock.currentTime);
}
static toolCreateFromSeconds(seconds) {
return DateTime.create(DTMod.fromSeconds(seconds));
}
static currentTimeSeconds() : number {
return PseudoClock.currentTime % DTCalc.spd;
}
spelltracker_queue
macro should now work.Note: You will need to redo this every time the About-Time module is updated, but luckily that module doesn't update very frequently.
If this doesn't work for you or if you don't want to edit module code, then you can use this simplified version of the macro below: spelltracker_queue_seconds.txt
This version should work (though I didn't test it, because I can't access Foundry from here), with the system time in seconds shown left of the character name of the tracked spells. (The only thing that the toolCreateFromSeconds()
function does, is convert that number of seconds into a date and a timestamp.)
Cheers!
Hello again,
I've been working with @Tarbarian on the Foundry PF1e Discord and this is what we've (99% Tarbarian) come up with. I have given Tarbarian your name to give credit in his comments.
/**
if (!actor) { ui.notifications.warn('You need to select a token before using this macro!'); //ends macro and warns if token not selected } else {
const CL =
token.actor.data.data.attributes.spells.spellbooks.primary.cl.total //Sets
the constant CL based on tokens primary caster level
let dialogContentlabel = <div><span style="flex:1">Spell Name: <input name="label" style="width:350px"/></span></div>
//text box for inputting
spell name
let dialogContentduration = <div><span style="flex:1">Custom Duration in Minutes (leave blank for none): <input name="duration" style="width:350px"/></span></div>
//text box for inputting duration
let d = new Dialog({ //
title: "Enter Spell Info", //
content: dialogContentlabel + dialogContentduration, //
buttons: { //
done: { //*
label: "Change!", //creates and
defines the dialog box
callback: (html) => {
if (isNaN(parseFloat(html.find("[name=duration]")[0].value)))
//determines if an integer was input for custom duration
{spellDurationNotification(html.find("[name=label]")[0].value, CL)} //if not, sets the custom duration same as one of the default, causing it to not be shown else
{spellDurationNotification(html.find("[name=label]")[0].value, parseFloat(html.find("[name=duration]")[0].value))} //if it is an int, sends it to the spell duration notification function } }, }, default: "done" }) d.render(true)
function spellDurationNotification(spellLabel, spellDuration) { const durationLengths = [1CL+" minutes", 10CL+" minutes", 1CL+" hours","24 hours",1CL+" days",spellDuration+" minutes"]; //sets the names of all the durations const durationNum = [1CL, 10CL, 60CL, 1440,1440CL, spellDuration]; //sets the durations in minutes const text = '
//beyond this point I don't really understand what it's doing, all this
is unmodified from the original macro let buttons = {}, dialog, content = text;
durationLengths.forEach((str)=> {
buttons[str] = {
label : str,
callback : () => {
const targetTime = game.Gametime.DTNow();
targetTime.minutes += durationNum[durationLengths.indexOf(str)];
sendChatMsg("In " + str + ", the " + spellLabel + " spell will
end!", str); const endedMsg = msgFormat(actor.getActiveTokens()[0].name + "'s " + spellLabel + " spell, has ended", "The " + spellLabel + " spell cast " + str + " ago, has ended.", str); const spellDurationId = game.Gametime.reminderAt(targetTime, endedMsg); // actor.setFlag("dnd5e","spellDurationId",spellDurationId); // console.log("Spell Tracker: added spell id", spellDurationId, "at", game.Gametime.getTimeString(),"(in-game time)"); dialog.render(true); } } });
dialog = new Dialog({title : 'Set spell duration', content,
buttons}).render(true); };
function msgFormat(isActiveMsg, msgContent, durationText) {
const htmlMsg = '<div><div class="dnd5e chat-card item-card">\
<header class="card-header flexrow red-header">\
<img
src="icons/tools/navigation/hourglass-yellow.webp" title="Spell Tracker" width="36" height="36">\
' + msgContent + '
While I was busy making a macro for tracking spell durations, I noticed that I was unable to access DateTime.js, DTCalc.js and DTMod.js directly through macro's in Foundry.
This was rather unfortunate, since that meant I'd have to duplicate the lengthy DateTime.normalize() function in my macro, which felt rather inefficient. That's why I decided to add this simple function to ElapsedTime.js starting at line 35:
This function is useful for macro users, because it can use a time value in seconds as an argument. This is great because the events stored in the queue accessible through
game.Gametime.ElapsedTime._eventQueue.array
store the event time_time
in seconds, whereas most functions in ElapsedTime.js make use of DTMod dictionaries.Another benefit of adding that function is that you can directly add and subtract seconds from the seconds variable before converting it to a DateTime dictionary, which in turn has built-in functions for formatting the result using
.shortDate()
or.longDate()
functions.I also want to add that there are a significant number of typing errors in the Readme.md file, specifically where the programmatic interface examples are listed. These are errors like missing a closing bracket, or using parentheses instead of brackets/accolades.
(Please note that I've only started dabbling with javascript since I bought Foundry two weeks ago, so I may have missed some kind of obvious way to access the functions from these classes. (maybe
game.Gametime.DTM()
and such, but it's unclear to me right now))Finally, thanks for making this module. Tracking time in my megadungeon crawl was a nightmare, this should help a ton.
Edit: I should add that I'm running Foundry v0.7.4 and About Time v0.1.58