Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
ValheimTwitch
Twitch interacts with Valheim through Streamer.bot: on-screen raid/follow/sub/gift/bits/redemption notifications, with mob & item spawning via configurable allowlists coming in Phase 2.
By CodeWithTD
| Date uploaded | 3 days ago |
| Version | 0.1.0 |
| Download link | CodeWithTD-ValheimTwitch-0.1.0.zip |
| Downloads | 12 |
| Dependency string | CodeWithTD-ValheimTwitch-0.1.0 |
This mod requires the following mods to function
denikson-BepInExPack_Valheim
BepInEx pack for Valheim. Preconfigured with the correct entry point for mods and preferred defaults for the community.
Preferred version: 5.4.2351ValheimModding-Jotunn
Jötunn (/ˈjɔːtʊn/, 'giant'), the Valheim Library was created with the goal of making the lives of mod developers easier. It enables you to create mods for Valheim using an abstracted API so you can focus on the actual content creation.
Preferred version: 2.30.2README
ValheimTwitch
A BepInEx 5 mod for Valheim that lets Twitch events interact with the game through Streamer.bot.
Plugin GUID: CodeWithTD.ValheimTwitch
Communication flow
Twitch -> Streamer.bot -> WebSocket -> ValheimTwitch mod -> Game actions
Valheim runs as a WebSocket client that connects to Streamer.bot's built-in WebSocket server, authenticates, and subscribes to events. Streamer.bot actions broadcast JSON commands to the mod; the mod validates them and executes game actions on the Unity main thread.
Status
Implemented (Phase 1)
- Compilable BepInEx 5 plugin (net462).
- Streamer.bot WebSocket client using the documented protocol:
Hellohandshake (Streamer.bot v0.2.5+) with legacy fallback.- OBS-style challenge authentication:
auth = base64(SHA256(base64(SHA256(password + salt)) + challenge)). Subscriberequest for Twitch events andGeneral.Custombroadcasts.- Auto-reconnect with configurable delay, graceful shutdown, diagnostics.
- Main-thread dispatch: every WebSocket callback is marshalled to the Unity main thread; nothing touches Unity/game state off-thread.
- Notifications through Valheim's own MessageHud, so they look exactly
like the game's built-in messages (local player only):
show_message- the top-left general text (title + message as consecutive messages, like the game's own top messages).show_notification- the large yellow center-screen message the game uses for prominent announcements.
- Automatic top-left notifications for Follow / Cheer / Sub / ReSub / GiftSub / GiftBomb / Raid / RewardRedemption (defensive field extraction).
- Structured command results and diagnostic logging; command handling fully decoupled from the WebSocket transport.
- Spawning, executed only with host/server authority (
ZNet.IsServer()), so spawned entities are networked and replicate to all players:mob_spawn- a mob type; e.g.Greydwarfspawns greylings, greydwarfs, shamans and brutes, spread randomly over the spawned amount with mixed levels (rolled per creature; optionallevelcaps the roll). Levels are set viaCharacter.SetLeveland stored on the ZDO so every player sees the stars.enemy_spawn- one specific creature prefab by name, also with optionallevel.item_spawn- items into the player's inventory or dropped on the ground (shared world), in stack-sized chunks.
- Curated vanilla prefab catalog (no bosses by default); unknown names are rejected. Spawn amounts are clamped to configured limits.
Not implemented
- Spawning from a pure client (joined someone else's server) is rejected by design; that would need a server-side component and an RPC protocol.
- Bosses are not in the catalog at all.
Project structure
ValheimTwitch/
├── ValheimTwitch.csproj
├── Plugin.cs # BepInEx plugin entry point, wiring, main-thread pump
├── Configuration/ModConfig.cs # All Config.Bind() entries
├── Networking/
│ ├── StreamerBotClient.cs # WebSocket client, auth, subscribe, reconnect
│ ├── StreamerBotEventParser.cs # Hello/response/event envelope parsing
│ ├── TwitchEventFormatter.cs # Twitch event -> notification text
│ └── MainThreadDispatcher.cs # Background -> Unity main-thread marshalling
├── Commands/
│ ├── CommandRouter.cs # Central router, handlers transport-independent
│ └── CommandModels.cs # Command payloads + defensive JSON extraction
├── Spawning/
│ ├── PrefabCatalog.cs # Mob categories, enemy names, item names
│ └── Spawner.cs # Server-authoritative spawn logic
├── UI/NotificationManager.cs # MessageHud-backed notifications
├── manifest.json # Thunderstore manifest
├── icon.png # Thunderstore icon (256x256)
├── CHANGELOG.md
├── package.ps1 # Build + Thunderstore zip packaging
└── README.md
Build
Requirements: .NET SDK 6/8/9 and a Valheim installation with BepInEx 5 (denikson's BepInExPack recommended).
dotnet build -c Release
Output goes to bin/Release/ (ValheimTwitch.dll, WebSocketSharp.dll,
Newtonsoft.Json.dll).
Assembly references
The project references, via MSBuild properties:
$(GameDir)\valheim_Data\Managed\assembly_valheim.dll(current Valheim builds keep the game code there;Assembly-CSharp.dllis only a stub)- BepInEx:
$(GameDir)\BepInEx\core\BepInEx.dllwhen it exists, otherwise the Mod Manager test profile'sBepInEx\core(ModProfileDir, the exact runtime version) - Unity modules from
$(GameDir)\unstripped_corlib\when it exists (then the profile'sunstripped_corlib, thenvalheim_Data\Managed)
Important: GameDir must point at the Steam install that actually runs
the game (check Steam\steamapps\libraryfolders.vdf). Compiling against a
stale copy of Valheim works fine but fails in-game with Method not found
errors when game method signatures have changed between versions.
Override paths if your setup differs:
dotnet build -c Release -p:GameDir="D:\Steam\steamapps\common\Valheim"
Installation & testing with r2modman / Thunderstore Mod Manager (recommended)
The mod packages as a standard Thunderstore mod, so the usual profile workflow works for testing:
-
Build the package (produces
dist/CodeWithTD-ValheimTwitch-<version>.zip):./package.ps1 # or: ./package.ps1 -GameDir "D:\...\Valheim"For a quick dev loop you can also deploy straight into a Mod Manager profile after building (works with Thunderstore Mod Manager and r2modman profiles; auto-detects either data folder):
./package.ps1 -Profile "Server" # or a full path: ./package.ps1 -ProfileDir "C:\...\profiles\Server"Directly deployed DLLs load normally but do not appear in the Mod Manager's mod list; import the zip instead for a managed/visible install.
-
In the Mod Manager: select your Valheim profile, then Import local mod (Mods screen, or drag-and-drop the zip onto the mod list), enable it, and launch the game with Start modded. If the profile does not have denikson's BepInExPack yet, the manager will offer to install it (it is declared as a dependency in
manifest.json). -
The mod's config lives inside the profile folder, not in the Steam install - edit it there:
%APPDATA%\Thunderstore Mod Manager\DataFolder\Valheim\profiles\<Profile>\BepInEx\config\CodeWithTD.ValheimTwitch.cfg %APPDATA%\r2modmanPlus-local\Valheim\profiles\<Profile>\BepInEx\config\CodeWithTD.ValheimTwitch.cfg(also check
...\BepInEx\LogOutput.login the same profile folder when verifying the WebSocket connection)
The zip uses the standard Thunderstore layout (manifest.json, icon.png,
README.md, CHANGELOG.md at the root plus BepInEx/plugins/ValheimTwitch/
with the mod DLLs and their dependencies), so publishing later only requires
uploading the same zip (e.g. via tcli publish) to the CodeWithTD team
namespace on Thunderstore.
Keep the version in sync in three places when it changes: Plugin.cs
(PluginVersion), manifest.json (version_number) and package.ps1
($Version) - package.ps1 fails the build if manifest.json disagrees.
Manual installation (Steam / BepInEx)
Copy into Valheim/BepInEx/plugins/ValheimTwitch/:
ValheimTwitch.dllWebSocketSharp.dllNewtonsoft.Json.dll
(or just build with -p:Deploy=true). Launch Valheim once to generate
BepInEx/config/CodeWithTD.ValheimTwitch.cfg, then edit it as needed.
Streamer.bot setup
- In Streamer.bot open Settings -> Streamerbot API -> Servers -> WebSocket Servers (older versions: Servers/Clients -> WebSocket Servers).
- Enable the server. Default host
127.0.0.1, default port8080- keep both unless you changed them, and keep the server on localhost. - Authentication is optional. If you enable it, set the same password in the
mod config (
[WebSocket] Password) - it is never written to the logs. - Make sure your Twitch account is connected so Twitch events flow in.
Test action for notifications
Create a new action (no trigger needed for testing):
- Add a C# sub-action (Execute C# Code) with:
using System;
public class CPHInline
{
public bool Execute()
{
string json = "{\"action\":\"show_message\",\"title\":\"RAID INCOMING!\",\"message\":\"Andilippi has raided with 42 viewers!\"}";
CPH.WebsocketBroadcastJson(json);
return true;
}
}
- Right-click the action in the action list and choose Execute (or bind a hotkey trigger). The notification must appear on the streamer's screen.
- For a real raid, add the same C# sub-action to an action with a Twitch -> Raided trigger - or skip the C# entirely: the mod already shows built-in top-left notifications for raids, follows, subs, gifts, bits and channel point redemptions automatically.
Command payloads (broadcast from any action)
Top-left general text (title + message as consecutive messages):
{"action":"show_message","title":"RAID INCOMING!","message":"Andilippi has raided with 42 viewers!"}
Large yellow center-screen message (the game's prominent announcement style):
{"action":"show_notification","message":"RAID INCOMING!"}
Spawn a mob type (variants spread over the amount; Greydwarf covers
greylings, greydwarfs, shamans and brutes). Levels are rolled per creature
(mostly normals, some stars); optional level sets the roll ceiling, so
"level":3 gives a mix of 1/2/3:
{"action":"mob_spawn","mob":"Greydwarf","amount":5,"level":3}
Spawn one specific enemy prefab at an exact level (1 = normal, 2 = one star, 3 = two stars):
{"action":"enemy_spawn","enemy":"Troll","amount":1,"level":2}
Give items to the streamer (or drop them in the shared world with
"delivery":"Ground"):
{"action":"item_spawn","item":"Wood","amount":10}
Spawned entities are networked (server-authoritative spawn through
ZNetScene.SpawnObject); when the mod runs on a pure client the spawn
commands are rejected. Amounts are clamped to [Spawning] MaxSpawnsPerCommand / MaxItemsPerCommand.
The full list of spawnable mob types, enemies and items lives in SPAWNS.md.
Verifying the WebSocket connection
Run Valheim and open BepInEx/LogOutput.log. On success you should see:
[Info : ValheimTwitch] [StreamerBot] Connecting to Streamer.bot at 127.0.0.1:8080 ...
[Info : ValheimTwitch] [StreamerBot] WebSocket connection opened; waiting for Streamer.bot Hello message...
[Info : ValheimTwitch] [StreamerBot] Connected to Streamer.bot instance '...' (version ...).
[Info : ValheimTwitch] [StreamerBot] Authenticated with Streamer.bot. (only when auth is enabled)
[Info : ValheimTwitch] [StreamerBot] Subscribed to Streamer.bot events (Twitch + General.Custom).
Streamer.bot also lists the connected client in its WebSocket server tab.
If nothing connects, check the [WebSocket] section of the config, that
Streamer.bot's server is enabled, and enable [Debug] Enabled +
[WebSocket] DebugLogging for raw message logging (the password is never
logged).
Configuration
Generated at BepInEx/config/CodeWithTD.ValheimTwitch.cfg on first launch:
[WebSocket]- Enabled, Host, Port, Password, AutoReconnect, ReconnectDelay, DebugLogging[Notifications]- Enabled (notifications are shown through Valheim's own MessageHud, so font, position and timing are the game's)[Spawning]- Enabled, MaxSpawnsPerCommand (default 10), MaxItemsPerCommand (default 100), SpawnDistance (default 10), MaxLevel (default 3 = two stars), ItemDelivery (Inventory or Ground)[Debug]- Enabled
Security notes
- Connects to localhost by default; the password is never logged.
- Only the five whitelisted commands are accepted; no arbitrary code or console commands are executed from WebSocket input.
- Spawn commands only ever execute with host/server authority; a pure client cannot spawn anything, and no multiplayer RPCs exist that a client could abuse.
- Strings are length-capped, spawn amounts are clamped to configured limits, and malformed messages are dropped without crashing.
- Notifications are local-only HUD messages.
CHANGELOG
Changelog
All notable changes to ValheimTwitch are documented here.
0.5.0
- Portraits reworked around one blank piece + per-piece viewer selection
(fixes placed portraits being invisible on Valheim 1.0 / Unity 6, where
the old per-viewer prefab activation no longer worked):
- One generic VT Portrait piece in the hammer's furniture menu. Place it blank, then press E on it to open the viewer picker and click a registered viewer; the piece immediately shows their avatar.
- The assignment is stored on the piece's own networked ZDO (vanilla
ZNetView.LoadFieldspersistence), so it survives reloads and syncs to every player with the mod; any player with the mod can reassign it. viewer_portraitnow registers/refreshes a viewer in the picker list (name + avatar URL) instead of creating a per-viewer build piece;viewer_portrait_removedrops a viewer from the list. Placed portraits keep showing their assigned viewer until reassigned or removed.[Portraits] MaxPortraitsnow caps the picker list (registered viewers), not build pieces.- The piece has a configurable build cost via
[Portraits] Cost(defaultWood:2, LeatherScraps:2, Raspberry:1, Mushroom:1, Blueberries:1); materials are refunded when removing it with the hammer. - The piece looks like a wooden tile (vanilla wood material, ItemDrawers style) with the avatar shown on the front; the viewer picker is styled after XPortal's panel (woodpanel, AveriaSerif, ValheimOrange header, dark dropdown-style rows).
- Diagnostics: placement, restore and avatar-download events are logged.
- Note: portrait pieces placed with 0.4.0 (per-viewer prefabs) will not restore in worlds saved with that version; remove them with the hammer before updating (they were invisible anyway on 1.0).
0.4.0
- Viewer-named enemies: optional
"viewer"field onmob_spawn/enemy_spawnwrites the viewer's name to the vanilla synced hover-name override (the tamed-pet name field), so every player sees e.g. "Andilippi's Troll" above the enemy's health bar. Rich-text characters are stripped from viewer names. - Twitch viewer portraits as buildable pieces:
viewer_portraitcommand ("name"+"url", https only): registers a framed "VT Portrait: <viewer>" piece in the hammer's furniture menu (free to build, rotates like any piece). The avatar is downloaded from the URL (e.g. the Twitch CDN image from Streamer.bot's Get User Info sub-action) and applied to the shared material.- Placed portraits are networked ZDOs, so they persist with the world;
the list of portrait pieces is kept in
BepInEx/config/CodeWithTD.ValheimTwitch.portraits.jsonand restored on world load. Remove placed copies with the hammer, andviewer_portrait_removedrops a viewer's piece from the menu. [Portraits] Enabled(default on) and[Portraits] MaxPortraits(default 10) config; portraits show for the host (co-op clients without the mod cannot resolve the custom prefab).
0.3.0
- Native Valheim raid events (host/server only; raids are shared world state
driven by the game's own
RandEventSystem, so HUD messages, music, spawners, durations and network sync are all vanilla behavior):raid_startcommand: a random native raid (the game's own roll, respecting biome/base/progression conditions) or a forced specific raid by name; rejected while another raid is in progress.raid_stopcommand: stops the raid currently in progress.[Raids] Enabledconfig gate.
- In-game
/request <item>chat command: resolves the item (multi-word requests like "fine wood" work), looks up a full stack size, and triggers a Streamer.bot action by name (WebSocketDoActionrequest) with the arguments%item%,%amount%and%player%- bind channel point costs, cooldowns, item granting, etc. in Streamer.bot. Action name is configured via[Requests] ActionName; no mod-side rate limit by design. - Root README.md restored (it had been overwritten with SPAWNS.md content).
0.2.0
- Manifest metadata refresh for Thunderstore.
0.1.0
- Initial Phase 1 baseline.
- Streamer.bot WebSocket client (Hello handshake, OBS-style challenge authentication, event subscriptions, auto-reconnect, graceful shutdown).
- Notifications through Valheim's own MessageHud (local player only, the
game's own font, position and timing):
show_messagecommand: top-left general text (title + message).show_notificationcommand: large yellow center-screen message.
- Automatic top-left notifications for Twitch follows, cheers, subs, resubs, gifted subs, gift bombs, raids and channel point redemptions.
- Spawning, executed only with host/server authority so spawned entities are
networked for all players:
mob_spawncommand: mob types (e.g. Greydwarf covers greylings, greydwarfs, shamans and brutes), variants and mixed levels rolled per creature, optional level ceiling.enemy_spawncommand: one specific enemy prefab, optional level.item_spawncommand: items into the inventory or dropped on the ground.
- Curated vanilla prefab catalog (bosses excluded); unknown names rejected, amounts clamped to configured limits.
- Configuration for WebSocket connection settings, spawning limits and debug logging.
- Thunderstore packaging (
package.ps1), with direct Mod Manager profile deploy for testing.
Not implemented yet
- Spawn and raid commands on a pure client (requires a server-side component and an RPC protocol; currently rejected by design).
- Bosses in the spawn catalog.