A six-component, WebSocket-coordinated, physics-simulated, Discord-integrated, Stream-Deck-controlled, Voicemod-triggered system for showing a little pop-up when someone rolls a dice.
When a player rolls a die on D&D Beyond, a 3D dice animation plays on a transparent overlay that every player at the table sees inside Owlbear Rodeo, a talking indicator lights up next to that player's avatar, the DM gets a notification on their phone, and a Voicemod sound effect triggers automatically based on the action name.
It took six separate applications to accomplish this.
Everything talks through a central WebSocket relay. The relay caches state so any client connecting mid-session immediately gets a full snapshot of the game. This is very smart engineering for an application whose primary output is a glowing eye graphic.
D&D Beyond (browser tab) └── dnd-dndbeyond-listener ──┐ rollDice, setHP │ Discord (voice channel) │ └── dnd-discord-bot ─────────┤ setTalk (FFmpeg + DirectShow) │ ▼ Stream Deck (physical hardware) dnd-websocket ──► dnd-overlay-obr (Owlbear Rodeo) └── dnd-stream-deck-plugin ──► (relay hub) ──► dnd-dm-web-ui (DM's phone) DM's phone / tablet └── dnd-dm-web-ui ────────────┘ rollDice, setHP, setDmImg, setInitiativeOrder, setHpBarOverride ...
What six months of questionable decisions looks like at the table.
A Node.js WebSocket relay server. Broadcasts events, caches state, replays it to late joiners. Deployed on Render so it stays running between sessions.
An Owlbear Rodeo extension. The DM installs it into the room once; every player who joins sees it automatically with zero setup on their end. Runs a full WebGL scene with cannon-es physics to throw 3D dice. Also shows HP bars, talking indicators, roll alerts, and the initiative order.
A mobile-first React app for the DM. Manages personas, dice rolls, initiative order (with drag-and-drop), HP tracking, overlay toggles, and Voicemod sounds. Has a compact mode for phone use during a live session.
Two Tampermonkey userscripts, both installed on the DM's browser only. Players install nothing — D&D Beyond's Game Log already delivers all player rolls to the DM's campaign page. The first script taps that. The second exists because Beyond20 has a convenient roll button on monster stat blocks, and switching to a different dice roller mid-combat was considered too much effort.
A Discord bot that monitors who is speaking in a voice channel and sends real-time setTalk events. Also captures a local audio device via FFmpeg and streams it into Discord — intended as a Voicemod pipe so sound effects play for everyone.
A full Elgato Stream Deck plugin with five custom actions: add dice, remove dice, reset dice, throw dice (public or private), and set DM portrait. Queues messages while disconnected and flushes on reconnect.
To experience a dice roll animation in your game, please ensure you have the following:
What do players have to install?
Nothing. The overlay is an Owlbear Rodeo extension. The DM installs it into the room once; every player who joins sees it automatically. Players use D&D Beyond from their phones like normal. Their rolls are captured from the DM's browser without them doing anything. The entire architecture was designed around the constraint that asking a group of adults to install a browser extension before game night is an invitation for someone to show up with nothing set up. Six projects is a reasonable price to pay for never having that conversation.
Why not just use Roll20 or Foundry?
The project was already using D&D Beyond for character sheets, Owlbear Rodeo for the virtual tabletop, and Discord for voice. None of them talk to each other natively. This is the connective tissue. Switching to Foundry would mean migrating everyone's characters, relearning a VTT, and abandoning a workflow that otherwise works. Building a six-project WebSocket ecosystem was clearly the more reasonable path.
Why does the dice animation use a physics engine if the result is already known?
Because watching dice tumble is the entire point. The physics simulation is not determining anything. It is theatre. This is understood and accepted.
Why are there two D&D Beyond userscripts? And do players have to install them?
Players install nothing. Both scripts run only on the DM's browser. D&D Beyond's Game Log WebSocket delivers every player's roll to the DM's campaign page anyway — the script just taps into that. Most players use D&D Beyond from their phones; asking them to install a browser extension was not going to happen.
The second script (beyond20.js) exists for one specific reason: rolling initiative for Goblins. Beyond20 has a convenient roll button on monster stat blocks. D&D Beyond's native UI doesn't offer the same convenience mid-combat. Rather than using the deck or the web UI dice roller for that one case, it was easier to also intercept Beyond20 events. This is laziness dressed up as a feature.
Why does the Discord bot pipe audio through FFmpeg?
So that Voicemod sound effects and background music play for everyone in the voice channel, not just people watching the stream. The DM's immersive sound design is non-negotiable. Also DirectShow was the available option on Windows and this project was not going to get simpler now.
Is this overengineered?
Yes. Every component is cleanly scoped, the protocol is well-documented, the reconnection logic is solid, and the state replay on connection is genuinely useful. It is overengineered in the same way a well-maintained vintage car is overengineered: it works, it's loved, and it was definitely more effort than taking the bus.
Can I use this for my own D&D campaign?
The player roster is currently hardcoded in the overlay. The WebSocket server is shared. The Stream Deck plugin UUID belongs to the developer. You would need to fork and reconfigure several things. This is considered a personal project and not a product. You have been warned.