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

[Script] Dialogue - Automated Chat Cutscenes

1535423596

Edited 1535497717
GM Michael
API Scripter
Due to an upcoming cutscene in my campaign (and my inability to do live multi-personality acting with six characters talking to each other), this evening I whipped this up.  Perhaps in the future, I'll update it to make it more user-friendly or make the code prettier, but for now, this is it. Simply put, this is a script that automates the printing of chat messages from NPCs, speaking as those NPCs.  The key feature is that the messages appear automatically on a delay based on length. FAQ Why not just use !delay?  !delay requires the DM to specify the time durations, whereas Dialogue does not.  Dialogue allows for a DM-specified reading speed (default=60ms per character), giving players ample time to read each message without requiring the DM to precompute everything.  You just write the message and Dialogue does the rest!  You can also alter playback speed while it's executing. Why not just print out the dialogue in one go?  Dialogue allows messaging to occur with dynamic delays between messages, both making it more immersive as it appears to be happening in real-time and also keeping speed-readers (or flip-to-the-end readers) from spoiling it for everyone else. Commands !dialogue -speed <speed> Sets the playback speed of the message.  Bigger number = slower playback. !dialogue -play Allows playback. !dialogue -stop Disallows playback.  If playback is currently executing, it is immediately halted. !dialogue UserA|UserA's message||UserB|UserB's message||UserC|UserC's Message Each message is separated by two pipes "||" and you separate the speaker from the message with a single pipe "|". Future Ideas Allow DM to play/pause playback at will without entirely aborting. Allow DM to pre-create dialogues that can be executed from a menu.  At present, if you want to have multiple dialogues, you'll need to have different macros pre-made or manually copy-paste them in. Allow conversation paths. Allow formatting of messages, possibly including avatar images if the speaker is a named NPC.  Alternatively, this could end up getting bundled into the Shopmaster update to Cashmaster, enabling scripted interactions with shopkeepers. I may never get around to those since this is pretty much a one-off, but if anyone ever wants to do it themselves, they're welcome to do so. Code /* * ============ * Dialogue * ============ * * Michael Greene * * A simple conversation script. * !dialogue -play * !dialogue -stop * !dialogue -speed <speed> * !dialogue Joe|short||Jill|medium ish message right here, thanks||Bill|i'ma gonna give a long message right here, oh yes sir I am. big bad joe jumped on the happy hill holiday.||Bill|I know this doesn't make any sense, but that's not the point. It's a demo.||Joe|You are correct. It doesn't make sense. */ on('ready', () => { const initDialogue = () => { // Initialize State object if (!state.Dialogue) { log('Initializing Dialogue'); state.Dialogue = { Play: true, Speed: 60 }; } }; const printLine = (lines, lineNumber) => { if (!state.Dialogue.Play || lineNumber === lines.length) { return; } const line = lines[lineNumber]; const segments = line.split('|'); const speaker = segments[0]; const message = segments[1]; log(`Sending ${speaker} as ${message}`); sendChat(speaker, message); // Order next message readNextLine(lines, lineNumber + 1, message.length); }; const readNextLine = (lines, lineNumber, lengthOfLastLine) => { const timeToWait = 600 + lengthOfLastLine * state.Dialogue.Speed; log(`Waiting for ${timeToWait}, then will initiate line[${lineNumber}].`); setTimeout(printLine, timeToWait, lines, lineNumber); }; on('chat:message', (msg) => { if (msg.type !== 'api') return; if (msg.content.startsWith('!dialogue') !== true) return; if (!playerIsGM(msg.playerid)) return; const parsedContent = msg.content.substr(msg.content.indexOf('!dialogue') + '!dialogue'.length + 1); const subcommands = parsedContent.split(';;'); subcommands.forEach((subcommand) => { if (!subcommand.includes('|')) { if (subcommand.includes('-play')) { state.Dialogue.Play = true; log('Allowing Playback'); sendChat('Dialogue', '/w gm Allowing Playback.'); } else if (subcommand.includes('-stop')) { state.Dialogue.Play = false; log('Stopping Playback'); sendChat('Dialogue', '/w gm Stopping Message.'); } else if (subcommand.includes('-speed')) { const speedString = subcommand.substr(subcommand.indexOf('-speed') + '-speed'.length + 1); const speed = parseInt(speedString); if(!isNaN(speed)) { state.Dialogue.Speed = speed; log(`Setting speed to ${speed}`); sendChat('Dialogue', `/w gm Setting speed to ${speed}.`); } } return; } if (!state.Dialogue.Play) { sendChat('Dialogue', '/w gm Playback currently paused, so Dialogue not initiated.'); } const lines = parsedContent.split('||'); readNextLine(lines, 0, 0); }); }); initDialogue(); log("Dialogue Started Up"); });
1535425146
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Nice idea!
1535427444
The Aaron
Pro
API Scripter
Interesting!    You can move const initDialogue ... inside the on('ready',...) function with the rest of them, you just have to place it before the call to initDialogue();   You could even move the call down next to the log() statement at the bottom.
keithcurtis said: Nice idea!
Perfect for making a tavern come alive or a walk in the city. Thanks Michael.
1535497781

Edited 1535497794
GM Michael
API Scripter
The Aaron said: Interesting!    You can move const initDialogue ... inside the on('ready',...) function with the rest of them, you just have to place it before the call to initDialogue();   You could even move the call down next to the log() statement at the bottom. Updated. Ravenknight said: Perfect for making a tavern come alive or a walk in the city. Thanks Michael. I hadn't thought of that!  It would be interesting to have as a sort of background noise for taverns and cities.