feat: Create initial Daggerheart stream overlay module with manifest, scripts, styles, templates, and localization files.
This commit is contained in:
commit
d516de52c0
8 changed files with 1693 additions and 0 deletions
23
README.md
Normal file
23
README.md
Normal 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
35
languages/de.json
Normal 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
41
languages/en.json
Normal 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
50
module.json
Normal 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
1266
scripts/module.js
Normal file
File diff suppressed because it is too large
Load diff
100
scripts/settings-app.js
Normal file
100
scripts/settings-app.js
Normal 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
144
styles/stream-overlay.css
Normal 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;
|
||||
}
|
||||
34
templates/settings-app.hbs
Normal file
34
templates/settings-app.hbs
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue