feat: Create initial Daggerheart stream overlay module with manifest, scripts, styles, templates, and localization files.

This commit is contained in:
CPTN Cosmo 2025-12-31 11:18:38 +01:00
commit d516de52c0
8 changed files with 1693 additions and 0 deletions

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# Daggerheart Stream Overlay
A FoundryVTT module designed for the Daggerheart system. This module creates a broadcast-ready `/stream` view that displays player cards, chat, fear tracker, and combat details.
## Features
### Stream Overlay
- **Broadcast Ready**: Accessible via `/stream`.
- **Auto-Layout**: Intelligently arranges content with a transparent chat on the left and player/game info on the right.
### Party & Player Cards
- **Dynamic Player Cards**: Automatically displays cards for all active players and the GM.
- **Live Updates**: Updates in real-time as players change HP, Stress, Hope, or their attributes.
- **Customizable Display**:
- Toggle visibility of Class, Subclass, Ancestry, Community, Resources, and Attributes.
- Choose between **Numeric** (3/6) or **Pips** (boxes) for resource display.
- Full or abbreviated attribute names.
- Customizable player name placement.
- **Spotlight Indicator**: Highlights the active player's card with a gold glow during combat.
### Fear Tracker
- **Integrated Tracker**: Displays the current Fear tokens at the bottom of the screen.
- **Theme Support**: Fully compatible with `dh-feartrackerplus` themes and settings (icons, colors, gradients). Falls back to a default Blood Moon theme if the module is missing.

35
languages/de.json Normal file
View file

@ -0,0 +1,35 @@
{
"DH_STREAM_OVERLAY": {
"Settings": {
"ResourceDisplayMode": "Anzeigemodus für Ressourcen",
"ResourceDisplayModeHint": "Wähle zwischen numerischen Werten (3/6) oder visuellen Pips.",
"ShowResourceIcons": "Ressourcen-Icons anzeigen",
"ShowResourceIconsHint": "Sichtbarkeit der Ressourcen-Icons (Herz, Blitz, Stern) umschalten.",
"ShowResources": "Ressourcen anzeigen",
"ShowResourcesHint": "Sichtbarkeit der gesamten Ressourcen-Zeile (HP, Stress, Hoffnung) umschalten.",
"ShowAttributes": "Attribute anzeigen",
"ShowAttributesHint": "Sichtbarkeit des Attributs-Rasters umschalten.",
"ShowClass": "Klasse anzeigen",
"ShowClassHint": "Sichtbarkeit der Charakterklasse.",
"ShowSubclass": "Subklasse anzeigen",
"ShowSubclassHint": "Sichtbarkeit der Charaktersubklasse.",
"ShowAncestry": "Abstammung anzeigen",
"ShowAncestryHint": "Sichtbarkeit der Charakterabstammung.",
"ShowCommunity": "Gemeinschaft anzeigen",
"ShowCommunityHint": "Sichtbarkeit der Charaktergemeinschaft.",
"Choices": {
"CharTop": "Charakter-Name Oben (Spieler Unten)",
"MenuLabel": "Stream-Overlay",
"PlayerNameDisplay": "Spielernamen-Anzeige"
},
"PlayerNameDisplayHint": "Wo der Spielername relativ zum Charakternamen angezeigt werden soll.",
"HideFearLabel": "Fear-Label verbergen",
"HideFearLabelHint": "Verbirgt die Textbeschriftung 'FEAR' neben den Fear-Tokens.",
"ShowResourceLabels": "Ressourcen-Bezeichnungen anzeigen",
"ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen."
},
"OpenStream": "Stream-Ansicht öffnen",
"ModeNumeric": "Numerisch (3/6)",
"ModePips": "Pips (Boxen)"
}
}

41
languages/en.json Normal file
View file

@ -0,0 +1,41 @@
{
"DH_STREAM_OVERLAY": {
"Settings": {
"ShowFullAttrNames": "Show Full Attribute Names",
"ShowFullAttrNamesHint": "Show full attribute names (Agility, Strength...) instead of abbreviations.",
"ResourceDisplayMode": "Resource Display Mode",
"ResourceDisplayModeHint": "Choose between numeric values (3/6) or visual pips.",
"ShowResourceIcons": "Show Resource Icons",
"ShowResourceIconsHint": "Toggle visibility of the resource icons (Heart, Bolt, Star).",
"ShowResources": "Show Resources",
"ShowResourcesHint": "Toggle visibility of the entire resources row (HP, Stress, Hope).",
"ShowAttributes": "Show Attributes",
"ShowAttributesHint": "Toggle visibility of the attributes grid.",
"ShowClass": "Show Class",
"ShowClassHint": "Toggle visibility of the character class.",
"ShowSubclass": "Show Subclass",
"ShowSubclassHint": "Toggle visibility of the character subclass.",
"ShowAncestry": "Show Ancestry",
"ShowAncestryHint": "Toggle visibility of the character ancestry.",
"ShowCommunity": "Show Community",
"ShowCommunityHint": "Toggle visibility of the character community.",
"UseGreenScreen": "Use Green Screen Background",
"UseGreenScreenHint": "If enabled, sets the background to bright green (#00ff00) for chroma keying. Otherwise, the background is transparent.",
"Choices": {
"CharTop": "Character Name Top (Player Bottom)",
"PlayerTop": "Player Name Top (Character Bottom)",
"NoPlayer": "Hide Player Name"
},
"MenuLabel": "Stream Overlay",
"PlayerNameDisplay": "Player Name Display",
"PlayerNameDisplayHint": "Where to show the player's name relative to the character name.",
"HideFearLabel": "Hide Fear Label",
"HideFearLabelHint": "Hide the text label 'FEAR' next to the fear tokens.",
"ShowResourceLabels": "Show Resource Labels",
"ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons."
},
"OpenStream": "Open Stream View",
"ModeNumeric": "Numeric (3/6)",
"ModePips": "Pips (Boxes)"
}
}

50
module.json Normal file
View file

@ -0,0 +1,50 @@
{
"id": "dh-stream-overlay",
"title": "Daggerheart Stream Overlay",
"version": "1.0.0",
"compatibility": {
"minimum": "13",
"verified": "13"
},
"authors": [
{
"name": "CPTN Cosmo",
"email": "cptncosmo@gmail.com",
"url": "https://github.com/cptn-cosmo",
"discord": "cptn_cosmo"
}
],
"relationships": {
"systems": [
{
"id": "daggerheart",
"type": "system",
"compatibility": {}
}
],
"requires": []
},
"esmodules": [
"scripts/module.js"
],
"languages": [
{
"lang": "en",
"name": "English",
"path": "languages/en.json"
},
{
"lang": "de",
"name": "Deutsch",
"path": "languages/de.json"
}
],
"styles": [
"styles/stream-overlay.css"
],
"packFolders": [],
"url": "https://github.com/cptn-cosmo/dh-stream-overlay",
"manifest": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/latest/download/module.json",
"download": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/download/1.0.0/dh-stream-overlay.zip",
"description": "A stream overlay module for Daggerheart that displays chat and a linked party actor sheet."
}

1266
scripts/module.js Normal file

File diff suppressed because it is too large Load diff

100
scripts/settings-app.js Normal file
View file

@ -0,0 +1,100 @@
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export class StreamOverlaySettings extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: "stream-overlay-settings",
tag: "form",
window: {
title: "DH_STREAM_OVERLAY.Settings.Title",
icon: "fas fa-video",
resizable: false,
contentClasses: ["standard-form"]
},
position: {
width: 400,
height: "auto"
},
form: {
handler: StreamOverlaySettings.#onSubmit,
closeOnSubmit: true
}
};
static PARTS = {
form: {
template: "modules/dh-stream-overlay/templates/settings-app.hbs"
}
};
async _prepareContext(_options) {
const actorUuid = game.settings.get("dh-stream-overlay", "partyActorUuid");
let actor = null;
if (actorUuid) {
actor = await fromUuid(actorUuid);
}
const dsnInstalled = game.modules.get("dice-so-nice")?.active;
const dsnEnabled = game.settings.get("dh-stream-overlay", "enableDSN");
return {
actorUuid,
actor,
dsnInstalled,
dsnEnabled
};
}
_onRender(context, options) {
super._onRender(context, options);
const html = this.element;
const dropZone = html.querySelector(".drop-zone");
if (dropZone) {
dropZone.addEventListener("dragover", this.#onDragOver.bind(this));
dropZone.addEventListener("dragleave", this.#onDragLeave.bind(this));
dropZone.addEventListener("drop", this.#onDrop.bind(this));
}
const clearBtn = html.querySelector(".clear-actor");
if (clearBtn) {
clearBtn.addEventListener("click", this.#onClear.bind(this));
}
}
#onDragOver(event) {
event.preventDefault();
event.currentTarget.classList.add("drag-over");
}
#onDragLeave(event) {
event.preventDefault();
event.currentTarget.classList.remove("drag-over");
}
async #onDrop(event) {
event.preventDefault();
event.currentTarget.classList.remove("drag-over");
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
if (data.type !== "Actor") return;
const actor = await fromUuid(data.uuid);
if (actor) {
await game.settings.set("dh-stream-overlay", "partyActorUuid", data.uuid);
this.render();
}
}
async #onClear(event) {
event.preventDefault();
await game.settings.set("dh-stream-overlay", "partyActorUuid", "");
this.render();
}
static async #onSubmit(event, form, formData) {
// Handle DSN setting
if (formData.object.enableDSN !== undefined) {
await game.settings.set("dh-stream-overlay", "enableDSN", formData.object.enableDSN);
}
}
}

144
styles/stream-overlay.css Normal file
View file

@ -0,0 +1,144 @@
/* Existing settings styles */
.stream-overlay-settings .drop-zone {
border: 2px dashed var(--color-border-light-2);
border-radius: 5px;
padding: 10px;
text-align: center;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-bg-light-1);
transition: background 0.2s, border-color 0.2s;
}
.stream-overlay-settings .drop-zone.drag-over {
background: var(--color-bg-light-2);
border-color: var(--color-border-highlight);
}
.stream-overlay-settings .actor-data {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.stream-overlay-settings .actor-name {
flex: 1;
text-align: left;
font-weight: bold;
}
.stream-overlay-settings .placeholder {
color: var(--color-text-light-2);
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
/* Stream View Layout */
body.dh-stream-overlay-active {
margin: 0;
padding: 0;
overflow: hidden;
background: transparent !important;
/* Allow OBS to key out background if needed */
}
#stream-overlay-wrapper {
display: flex;
flex-direction: row;
width: 100vw;
height: 100vh;
box-sizing: border-box;
padding: 10px;
gap: 10px;
}
/* Panels */
.stream-panel {
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
}
#stream-chat-container {
flex: 0 0 320px;
min-width: 300px;
}
#stream-combat-container {
flex: 0 0 300px;
min-width: 250px;
/* Hidden by default in JS if not active, or we can transition it */
transition: flex-basis 0.3s ease, padding 0.3s ease;
}
#stream-actor-container {
flex: 1;
min-width: 500px;
background: rgba(255, 0, 0, 0.2);
/* DEBUG RED TINT */
border: 2px solid red;
/* DEBUG BORDER */
position: relative;
/* Sheet usually has its own background */
}
/* Inner adjustments */
#chat-log,
.chat-log {
flex: 1;
overflow-y: scroll;
padding: 5px;
padding-right: 20px;
/* Space for scrollbar */
background: transparent !important;
margin: 0;
border: none;
list-style: none;
/* remove bullets */
}
/* Ensure messages wrap correctly */
.chat-log .message {
margin: 5px 0;
padding: 8px;
background: rgba(0, 0, 0, 0.5);
/* semi-transparent background for readability */
border-radius: 5px;
color: white;
word-wrap: break-word;
word-break: break-word;
/* Ensure long words don't overflow */
max-width: 100%;
}
/* Override window-app styles for embedded content */
#stream-overlay-wrapper .window-app {
box-shadow: none !important;
background: rgba(30, 30, 30, 0.9) !important;
/* Unified dark theme backing */
margin: 0 !important;
}
#stream-actor-container .window-content {
background: rgba(255, 255, 255, 0.95);
/* Ensure legibility */
border-radius: 8px;
height: 100%;
}
/* Combat tracker specific */
#combat-tracker {
height: 100%;
margin: 0;
}
#combat-tracker .combatant {
line-height: 2em;
}

View file

@ -0,0 +1,34 @@
<div class="stream-overlay-settings">
<p class="notes">{{localize "DH_STREAM_OVERLAY.Settings.Instructions"}}</p>
<div class="form-group">
<label>{{localize "DH_STREAM_OVERLAY.Settings.PartyActor"}}</label>
<div class="drop-zone" data-dtype="Actor">
{{#if actor}}
<div class="actor-data">
<img src="{{actor.img}}" title="{{actor.name}}" width="36" height="36" />
<span class="actor-name">{{actor.name}}</span>
<button type="button" class="clear-actor" title="{{localize " Delete"}}">
<i class="fas fa-trash"></i>
</button>
</div>
{{else}}
<div class="placeholder">
<i class="fas fa-user"></i>
<span>{{localize "DH_STREAM_OVERLAY.Settings.DropActorHere"}}</span>
</div>
{{/if}}
<input type="hidden" name="partyActorUuid" value="{{actorUuid}}">
</div>
</div>
{{#if dsnInstalled}}
<div class="form-group">
<label>
<input type="checkbox" name="enableDSN" {{checked dsnEnabled}}>
Enable Dice So Nice Animations
</label>
<p class="notes">If enabled, 3D dice rolls will appear on the stream overlay.</p>
</div>
{{/if}}
</div>