Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Haunting Script - Enjoy!!

Hey Guys, Here's a script to have a little fun with.  All you need is one character with a "Haunting" Attribute (the sentient weapon or other worldly spirit, etc), the name of another character that gets whispered to as the value for that Attribute, and a RollableTable populated with phrases for the haunter to whisper to the hauntee. Enjoy! // HauntedTelepathy.js by Christopher Craig v1.0 20200421 // //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 characters with a 'Haunting' attribute to the characters named in that attribute.  Based on normal  //                  Roll20 mechanics, all players who can control and edit the character named in the Haunting attribute, will see the whisper appear in their  //                  chat window.  // on("ready",function() { log("HT checkpoint 1:  Sandbox Ready");     var myVar = setInterval(funHauntings, 300000);     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;             };             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;             };             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);         };     }; }); Sorry for all of the log statements being out of format, but I do that on purpose so I can quickly find the right part of the code when troubleshooting.
1587524049
GiGs
Pro
Sheet Author
API Scripter
Thats a fun idea for a script.
This would be useful for one of those cartoonish scenarios where you see a little angel sitting on one shoulder of a person, and a little devil sitting on the person's other shoulder, each trying to persuade the the person to act good or evil.  One character named "The angel on your right shoulder", the other character named "The devil on your left shoulder" both haunting the same player character.  There's gotta be list of sayings for each of those RollableTables out there somewhere on the internet.
1587561157

Edited 1587561246
GM Michael
API Scripter
So if i'm reading this correctly, every 5 minutes, you whisper a random entry from a rollable table?  Sounds like it could be fun, though i could see it getting pretty darn spammy.  There are some scripts that the GM has to add themselves as a character owner for, so it might be best to not have everyone see it, or just to have a configurable rate. Also, I don't suppose anyone knows of a script for bulk rollable table import?
1587562451
GiGs
Pro
Sheet Author
API Scripter
GM Michael said: Also, I don't suppose anyone knows of a script for bulk rollable table import? This would be better asked in another thread, but the is such a script, called something like: Table Import/Export
@GM Michael, This script only whispers to one character per Haunting attribute found.  So it would only get spammy for the one person controlling that one character named in the Haunting attribute.  Don't add the Haunting attribute to every character! (that'll slow things way down).  Just add the Haunting attribute to only the one character representing the sentient magic item, or the other worldly being doing the haunting.  Also you can modify the time interval between whispers by changing the last number in the   var myVar = setInterval(funHauntings, 300000);  line.  That's the number of milliseconds between intervals, so 1 minute = 60000.  You can just multiply that times the number of minutes desired and replace the 300000 with it.
1587750561

Edited 1587750665
GM Michael
API Scripter
GiGs said: This would be better asked in another thread, but the is such a script, called something like: Table Import/Export I was asking in this thread since it seemed like a relevant synergy with the posted.  I was mainly asking for posterity if someone comes through here in the future. Chris C. said: @GM Michael, This script only whispers to one character per Haunting attribute found.  So it would only get spammy for the one person controlling that one character named in the Haunting attribute.  Don't add the Haunting attribute to every character! (that'll slow things way down).  Just add the Haunting attribute to only the one character representing the sentient magic item, or the other worldly being doing the haunting.  Ah, I had it backwards with the way the references worked.  Cool. Another concern though: I know I for one stay logged in for extended periods, just leaving the tab open and work on prepping the next session throughout the day, keeping the API active for quite a while.  In situations like that, would the player log in to find hundreds of messages?  I didn't notice anything in the script stopping that.  I'd imagine the simplest approach would be just to use noarchive?
@GM Michael - Oh boy!  Didn't think about letting it run while working on other projects in the campaign.  Well, if my one player that I had this in mind for complains of Roll20 being sooooooo slooooowww tomorrow night, I think we'll know why.  LOL.  Oh well, that's what he get's for having his character steal a spell book from an Illithid Arcanist (the sentient, telepathic magic item). Seriously, it would probably be best to just disable this particular script until it's game-time.  I think I'll go do that now.   
This script looks awesome! Would be really useful for my WW2-style game where the radio operator will have to sift through random radio messages and try to make sense of it all. How difficult would it be to make it chat activated? Like I write a chat command to start the script and then another command to stop it? I would imagine it wouldn't be too hard, just copying a similar function from another script that handles it and inserting the code you already have, but I could be wrong, and such a function might require many fancy steps to work? Either way, really neat script!
*****I can not write scripts and do not know the abilities/limitations of the API***** Could the API detect if the controlling player is actually logged in and only spam the character when the player is active?
@Cindurion I aim to make the affects of my scripts largely controllable by means other than API programming.  In this case, the GM can always change the value of the 'Haunting' attribute of the haunting item to nothing (delete the value), then the affect won't happen.  So is it possible to get THAT (changing the value of the 'Haunting' attribute) chat activated?  Yes, quite easily, I'm sure.  I just don't have it built-in to this script.  It can easily be scripted independently of this script with another API script, though. Now that I'm thinking about it, I'm wondering if a "Haunt now" function would also be a desirable part of this. In your scenario for instance, you may want the first haunting to happen as soon as the character turns on the radio.  @Axecleft FYI:  I am new to Roll20 and programming in Java Script (the language behind the API) but I have plenty of programming experience in other languages.  From what I've seen in the in the Roll 20 programming references, yes it is possible.  The real question is getting the time to figure out all of the intricacies of parsing the comma delimited lists in the character object's 'controlledby' property, then running a test on all of the playerIDs within it on their _online property.   Hmmmm.  I really like this potential feature which will help save potential run-time overhead for the campaign without the GM having to disable the script (an issue discussed by @GM Michael and I earlier). Okay Guys, let me think about these features and see what I can do over the next couple of days.
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.
Awesome, this is great! This will work wonderfully for my campaign! I took the liberty of changing it out so that it would accept numbers lower than 1 since I will probably need messages coming in around every 30 sec. I also added a randomizer to make sure it doesn't come in at every 30 sec exactly, though the randomizer only randomizes the first time I run the command, ideally the randomizer would randomize bertween each "haunt" but this works more than great for my uses so far! Awesome work, thanks!
Hmm, would it be possible to have the script delete the row of the table after a successful haunt, to make sure the haunted player gets a unique haunt each time? (and if the table is empty, it doesn't haunt?) Is that something that is accessible via API?
Or even just set the weight of that row to 0? Should have the same effect, I'm guessing?