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 .
×
May your rolls be chill this holiday season!
Create a free account

Make The Map an Interactive "CRT Screen"

I have a campaign coming up where I intend to have the players periodically interact with a person who contacts them via an old computer terminal using a green phosphor CRT. To do this I want to replace the map field with a CRT, and have the person interact with the players on the "map" using text. This character cannot speak normally for... Reasons. And I want to use Roll20 to do that bit of story-telling visually for me... Rather than me simply describing it. To have it be as effective as possible, I want to be able to easily interact with the screen by typing. Preferably in a way that the players can see the characters appear on the "screen" one at a time. At the same time it would be nice to have the players be able to interact with the screen in the same way. (The messages this character will send will be simple, and may occur as the players are interacting with other people or doing other things... ie I will be talking to them as the DM or NPC and have the screen pop up messages at the same time, for example... "DONT TRUST HIM!" while a person is talking to the group. Etc.) I obviously can use chat for this purpose, but this will be occurring outside of combat encounters, and will be a key mystery and character the players will interact with during the campaign. "Who's the man behind the screen?" Is there anything already out there that could be used to make this work?
1765524192
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Hi Inspired Ogre! I wrote this scriptlet using chatGPT in about ten minutes, so I make no guarantees. It looks better in dark mode, where the CRT "window" is not surrounded by handout white. Any player can type !crt something cool here. This will create a live updating handout called "CRT". Every time you enter a line, the handout will update. It will use whatever the typer's "Speaking As" is set to at the bottom of chat. So the GM could types as any character in the game. To clear the handout, type: !crt --clear  Only the GM can clear the handout. Since these are API calls, they will not appear in chat and will only appear in the handout. If you want to create a new, fresh conversation, but preserve the old one, just make a copy of the existing CRT handout. Here is an animated demo. And here is the code: on('ready', () => {     const HANDOUT_NAME = "CRT";     const sanitize = (str) => {         return (str || "").replace(/[<>&"]/g, c => ({             '<': '<', '>': '>', '&': '&', '"': '"'         })[c]);     };     const CRT_WRAPPER_START = `<div id="crt" style="background:black;color:#33ff33;font-family:monospace;font-size:14px;padding:10px;">`;     const CRT_WRAPPER_END = `</div>`;     const baseCRTContent = `${CRT_WRAPPER_START} ${CRT_WRAPPER_END}`;     const getOrCreateHandout = () => {         let ho = findObjs({ type: 'handout', name: HANDOUT_NAME })[0];         if (!ho) {             ho = createObj("handout", { name: HANDOUT_NAME });             ho.set("notes", baseCRTContent);         }         return ho;     };     const clearHandout = () => {         const ho = getOrCreateHandout();         ho.set("notes", baseCRTContent);     };     const appendToHandout = (speaker, text) => {         const ho = getOrCreateHandout();         ho.get("notes", notes => {             let content = notes || "";             const startIndex = content.indexOf(CRT_WRAPPER_START);             const endIndex = content.lastIndexOf(CRT_WRAPPER_END);             if (startIndex === -1 || endIndex === -1) {                 content = baseCRTContent;             }             const before = content.slice(0, content.lastIndexOf(CRT_WRAPPER_END));             const after = CRT_WRAPPER_END;             const newLine = `${speaker}: ${text}<br>\n`;             const updated = before + newLine + after;             ho.set("notes", updated);         });     };     on('chat:message', msg => {         if (msg.type !== 'api') return;         if (!msg.content.startsWith('!crt')) return;         const parts = msg.content.trim().split(/\s+/);         // CLEAR COMMAND         if (parts[1] === "--clear") {             if (!playerIsGM(msg.playerid)) {                 sendChat("CRT", "/w " + msg.who + " Only the GM may clear the CRT.");                 return;             }             clearHandout();             sendChat("CRT", "/w gm CRT handout cleared.");             return;         }         // NORMAL APPEND MODE         const args = msg.content.replace(/^!crt\s*/, "");         if (!args.trim()) return;         const text = sanitize(args.trim());         let speaker = msg.who ? msg.who.replace(/\s*\(GM\)\s*$/, "") : "Unknown";         if (msg.characterid) {             const char = getObj("character", msg.characterid);             if (char) {                 speaker = char.get("name");             }         }         appendToHandout(speaker, text);     }); });
1765524301
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I considered having it update character-by-character, but a 40 character line would update the handout 40 times. That would be slow, and probably screw up somewhere.
Thanks for this, Keith! This implementation gets me 80% of the way there.  Originally in my minds eye I would be using a CRT png asset with a semi-transparent screen as a foreground object with text in the map layer. However, trying to manage text objects is just too fiddly to make sense (and I certainly would not ask the players to do it.) The risk of doing it manually is ruining the effect... Having to nudge the text objects into alignment, manage line width/character limits, font and color, etc. I have been through the mod library a few times and haven't found any mods that use/edit text on the play field. Maybe its just not done, or there is some technical reason. At any rate this implementation loses some of the immersion, but gains a lot in the process. Any way to add an extra line between each !crt command?  Me playing around with it. BTW Drave was the randomized name that Roll20 came up with after hitting the "create character" button. Funny given the context.
1765664601
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
This should add a line: Change         const text = sanitize(args.trim()); to:         const text = sanitize(args.trim())+"<BR>";
Wow Keith, thats really awesome.