Version 2.0 with some interaction built in to turn the affect on or off, immediately conduct a round of Haunting whispers (Haunts), and to set the interval between Haunts. This script will also test for at least one player online that controls a character being whispered to. If there isn't at least one online, then that haunting whisper is not sent at all. Pay attention to your API log to see results of each Haunt. // HauntedTelepathy.js by Christopher Craig v2.0 20200524 // //Program Contract: //Requirement 1: There must be at least one character item (the sentient magic item or other-worldly, haunting being) with an Attribute named "Haunting" and the // current field of the Attribute must be populated with a name of another player-controlled character in the campaign. //Requirement 2: There must be a Rollable Table created with the same name as the character having the "Haunting" attribute in Requirement one with // "-Haunting-Rants" added to the end of the table name. This table name must also have any spaces replaced with dashes. //Requirement 3: There must be at least one entry in the table. Entries consist of plain text messages that are whispered to the Haunted character named in the // "current" field of the Haunting attribute discussed in Requirement 1. The entries may have different weights associated with them according // to normal Roll20 mechanics based on whether some messages would be whispered more often than others. // //Output: The script will generate whispered messages from the character with a 'Haunting' attribute to the characters named in that attribute. Based on normal // Roll20 mechanics, all players who can control the character named in the Haunting attribute, will see the whisper appear in their // chat window. // //Usage: !Haunt [On|Off|Now|{interval}] // !HT [On|Off|Now|{interval}] // // On - Turn on Haunting from all character items with a 'Haunting' attribute. This will also reset the clock for the time to the next Haunting. // Off - Turn off all Haunting. // Now - Immediately conduct a round of Hauntings from all character items with a 'Haunting' attribute. This is an independant action not tied to the // regular, periodic Hauntings. This will not affect the timing for the next regularly scheduled Haunting. // {interval} - a number 1 - 30 representing the number of minutes between Hauntings. This command will also reset the clock to the given value for the // next round of Hauntings. // //Note 1: You may put more than one "Haunting" attribute on a single character if you want that character to haunt multiple player characters, but the messages // will be different to each of the player characters. This script cannot haunt different players using the same message on each haunt to the // different characters {Sorry :( }. //Note 2: When deciphering commands, this script looks at only the first two characters after the first space. This means that the first three commands listed // above can be abbreviated with just the first two characters. This also means that the script will only read the first two characters of any // numbers when executing the {interval} command so '!Haunt 30' and '!Haunt 308746' will both have the same result. on("ready",function() { log("HT checkpoint 1: Sandbox Ready"); var hauntInterval = 300000; var timedHaunt = setInterval(funHauntings, hauntInterval); on("chat:message",function(msg){ log("HT checkpoint 1.1: Chat message detected"); if(msg.type=="api" && (msg.content.indexOf("!Haunt")==0||msg.content.indexOf("!HT")==0)) { log("HT checkpoint 1.2: API command detected"); if (!playerIsGM(msg.playerid)) { log("HT checkpoint 1.3: Command entered by Non-GM player"); return; } else { var strCommand = msg.content.toUpperCase().substr(msg.content.indexOf(" ")+1,2); log("HT checkpoint 1.4: strCommand: "+strCommand); switch (strCommand) { case 'ON': clearInterval(timedHaunt); timedHaunt = setInterval(funHauntings, hauntInterval); sendChat("HT","/w GM Hauntings turned ON and reset to run every "+hauntInterval/60000+" minutes."); break; case 'OF': clearInterval(timedHaunt); sendChat("HT","/w GM Hauntings turned OFF"); break; case 'NO': sendChat("HT","/w GM Proceeding with an extra round of Hauntings now."); funHauntings(); break; default: var numCommand = Number(strCommand); log("HT checkpoint 1.5: numCommand: "+numCommand); if (isNaN(numCommand)) { sendChat("HT", "/w GM "+strCommand+" is not a valid Haunted Telepathy command. Try ON, OFF, NOW, or 01-30."); } else if (numCommand >= 1 && numCommand <= 30) { hauntInterval = numCommand*60000; clearInterval(timedHaunt); timedHaunt = setInterval(funHauntings, hauntInterval); sendChat("HT","/w GM Hauntings turned ON and reset to run every "+hauntInterval/60000+" minutes."); } else { sendChat("HT", "/w GM "+numCommand+" is not a valid Haunted Telepathy timer interval. Try 01 - 30."); }; break; }; return; }; }; }); function funHauntings() { log("HT checkpoint 2: Let the Hauntings Begin! "+Date()); var arrHauntingAttributes = findObjs({name: "Haunting",_type: "attribute"}, {caseInsensitive: false}); if (arrHauntingAttributes===undefined) { log("HT checkpoint 3: No Haunting attributes found. This round of Hauntings skipped."); return; }; arrHauntingAttributes.forEach(funWhispers); log("HT checkpoint 4: End of Hauntings - For now - Muuuwaaahaahahahaaa!!"); function funWhispers(objAttribute, index) { log("HT checkpoint 5: Proceeding with haunting #"+(index+1)); objWhisperFrom = findObjs({_type:"character",_id:objAttribute.get("_characterid")})[0]; var strWhisperFromName = objWhisperFrom.get("name"); var strWhisperFromID = "character|"+objWhisperFrom.get("_id"); strTableName = objWhisperFrom.get("name").replace(/ /g, "-")+"-Haunting-Rants"; var objTable = findObjs({_type:"rollabletable", name:strTableName})[0]; if (objTable===undefined) { log("HT checkpoint 6: "+strTableName+" table not found"); return; }; arrWhisperToNames = objAttribute.get("current").split(","); objWhisperTo = findObjs({_type:"character",name:objAttribute.get("current")})[0]; if (objWhisperTo===undefined) { log("HT checkpoint 7: Invalid character name entered in "+strWhisperFromName+"'s 'Haunting' attribute"); return; }; var arrPlayerIDs = objWhisperTo.get("controlledby").split(","); var boolPlayerOnline = false; var i = 0; var objPlayer; log("HT checkpoint 7.2: Checking for players online that control characters haunted by "+strWhisperFromName); while (i < arrPlayerIDs.length && boolPlayerOnline==false) { objPlayer = getObj('player',arrPlayerIDs[i]); boolPlayerOnline = objPlayer.get("online"); i++; }; if (boolPlayerOnline) { strWhisperTo = objAttribute.get("current"); if (strWhisperTo.indexOf(" ")!=-1) { strWhisperTo=strWhisperTo.substring(0, strWhisperTo.indexOf(" ")); }; sendChat (strWhisperFromID,"/w "+strWhisperTo+" [[1t["+strTableName+"]]]"); log("HT checkpoint 8: "+strWhisperFromName+" whispered to "+strWhisperTo); } else { log("HT checkpoint 8.2: No players online that control "+objAttribute.get("current")+". No message whispered.") }; }; }; }); Enjoy! If you have any questions, just post them here.