added additional layouts and carousel mode
This commit is contained in:
parent
d516de52c0
commit
4a550233c0
4 changed files with 519 additions and 46 deletions
|
|
@ -19,14 +19,34 @@
|
||||||
"ShowCommunityHint": "Sichtbarkeit der Charaktergemeinschaft.",
|
"ShowCommunityHint": "Sichtbarkeit der Charaktergemeinschaft.",
|
||||||
"Choices": {
|
"Choices": {
|
||||||
"CharTop": "Charakter-Name Oben (Spieler Unten)",
|
"CharTop": "Charakter-Name Oben (Spieler Unten)",
|
||||||
|
"PlayerTop": "Spieler-Name Oben (Charakter Unten)",
|
||||||
|
"NoPlayer": "Spieler-Name ausblenden",
|
||||||
"MenuLabel": "Stream-Overlay",
|
"MenuLabel": "Stream-Overlay",
|
||||||
"PlayerNameDisplay": "Spielernamen-Anzeige"
|
"PlayerNameDisplay": "Spielernamen-Anzeige",
|
||||||
|
"Standard": "Standard (Breitbild)",
|
||||||
|
"Vertical": "Vertikal (Sammelkarte)",
|
||||||
|
"RefFade": "Ausblenden (Fade)",
|
||||||
|
"RefSlide": "Gleiten (Slide)",
|
||||||
|
"RefZoom": "Zoomen (Zoom)",
|
||||||
|
"RefCycle": "Zyklus (In Rotation)",
|
||||||
|
"RefStatic": "Statisch (Immer sichtbar)",
|
||||||
|
"RefHidden": "Versteckt (Im Karussell)"
|
||||||
},
|
},
|
||||||
"PlayerNameDisplayHint": "Wo der Spielername relativ zum Charakternamen angezeigt werden soll.",
|
"PlayerNameDisplayHint": "Wo der Spielername relativ zum Charakternamen angezeigt werden soll.",
|
||||||
"HideFearLabel": "Fear-Label verbergen",
|
"HideFearLabel": "Fear-Label verbergen",
|
||||||
"HideFearLabelHint": "Verbirgt die Textbeschriftung 'FEAR' neben den Fear-Tokens.",
|
"HideFearLabelHint": "Verbirgt die Textbeschriftung 'FEAR' neben den Fear-Tokens.",
|
||||||
"ShowResourceLabels": "Ressourcen-Bezeichnungen anzeigen",
|
"ShowResourceLabels": "Ressourcen-Bezeichnungen anzeigen",
|
||||||
"ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen."
|
"ShowResourceLabelsHint": "Text-Bezeichnungen (HP, Stress, Hoffnung) neben Icons anzeigen.",
|
||||||
|
"CardLayout": "Karten-Layout Modus",
|
||||||
|
"CardLayoutHint": "Wähle zwischen der Standard-Breitbild-Karte oder einem vertikalen Sammelkarten-Layout.",
|
||||||
|
"CarouselEnabled": "Karussell-Modus aktivieren",
|
||||||
|
"CarouselEnabledHint": "Wechsle automatisch nacheinander durch Spieler.",
|
||||||
|
"CarouselSpeed": "Karussell-Geschwindigkeit (s)",
|
||||||
|
"CarouselSpeedHint": "Sekunden, die jede Karte angezeigt wird, bevor gewechselt wird.",
|
||||||
|
"CarouselEffect": "Übergangseffekt",
|
||||||
|
"CarouselEffectHint": "Animationseffekt beim Wechseln der Karten.",
|
||||||
|
"CarouselGMMode": "GM Karussell-Verhalten",
|
||||||
|
"CarouselGMModeHint": "Wie der GM im Karussell behandelt werden soll."
|
||||||
},
|
},
|
||||||
"OpenStream": "Stream-Ansicht öffnen",
|
"OpenStream": "Stream-Ansicht öffnen",
|
||||||
"ModeNumeric": "Numerisch (3/6)",
|
"ModeNumeric": "Numerisch (3/6)",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,15 @@
|
||||||
"Choices": {
|
"Choices": {
|
||||||
"CharTop": "Character Name Top (Player Bottom)",
|
"CharTop": "Character Name Top (Player Bottom)",
|
||||||
"PlayerTop": "Player Name Top (Character Bottom)",
|
"PlayerTop": "Player Name Top (Character Bottom)",
|
||||||
"NoPlayer": "Hide Player Name"
|
"NoPlayer": "Hide Player Name",
|
||||||
|
"Standard": "Standard (Horizontal Card)",
|
||||||
|
"Vertical": "Vertical (Trading Card)",
|
||||||
|
"RefFade": "Fade",
|
||||||
|
"RefSlide": "Slide",
|
||||||
|
"RefZoom": "Zoom",
|
||||||
|
"RefCycle": "Cycle (In Rotation)",
|
||||||
|
"RefStatic": "Static (Always Visible)",
|
||||||
|
"RefHidden": "Hidden (In Carousel)"
|
||||||
},
|
},
|
||||||
"MenuLabel": "Stream Overlay",
|
"MenuLabel": "Stream Overlay",
|
||||||
"PlayerNameDisplay": "Player Name Display",
|
"PlayerNameDisplay": "Player Name Display",
|
||||||
|
|
@ -32,7 +40,21 @@
|
||||||
"HideFearLabel": "Hide Fear Label",
|
"HideFearLabel": "Hide Fear Label",
|
||||||
"HideFearLabelHint": "Hide the text label 'FEAR' next to the fear tokens.",
|
"HideFearLabelHint": "Hide the text label 'FEAR' next to the fear tokens.",
|
||||||
"ShowResourceLabels": "Show Resource Labels",
|
"ShowResourceLabels": "Show Resource Labels",
|
||||||
"ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons."
|
"ShowResourceLabelsHint": "Show text labels (HP, Stress, Hope) next to icons.",
|
||||||
|
"AutoExpandChat": "Auto-Expand Chat Messages",
|
||||||
|
"AutoExpandChatHint": "Automatically expand all chat messages to show full content (rolls, descriptions) without needing to click.",
|
||||||
|
"HideChatActions": "Hide Chat Action Buttons",
|
||||||
|
"HideChatActionsHint": "Hides buttons like 'Apply Damage', 'Place Template', or other interactive elements from chat cards to keep the view clean.",
|
||||||
|
"CardLayout": "Card Layout Mode",
|
||||||
|
"CardLayoutHint": "Choose between the standard wide card or a vertical trading-card style.",
|
||||||
|
"CarouselEnabled": "Enable Carousel Mode",
|
||||||
|
"CarouselEnabledHint": "Cycle through players one by one automatically.",
|
||||||
|
"CarouselSpeed": "Carousel Speed (s)",
|
||||||
|
"CarouselSpeedHint": "Seconds to show each player card before switching.",
|
||||||
|
"CarouselEffect": "Transition Effect",
|
||||||
|
"CarouselEffectHint": "Animation effect when switching cards.",
|
||||||
|
"CarouselGMMode": "GM Carousel Behavior",
|
||||||
|
"CarouselGMModeHint": "How the GM should constitute in the carousel."
|
||||||
},
|
},
|
||||||
"OpenStream": "Open Stream View",
|
"OpenStream": "Open Stream View",
|
||||||
"ModeNumeric": "Numeric (3/6)",
|
"ModeNumeric": "Numeric (3/6)",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "dh-stream-overlay",
|
"id": "dh-stream-overlay",
|
||||||
"title": "Daggerheart Stream Overlay",
|
"title": "Daggerheart Stream Overlay",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "13",
|
||||||
"verified": "13"
|
"verified": "13"
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
],
|
],
|
||||||
"packFolders": [],
|
"packFolders": [],
|
||||||
"url": "https://github.com/cptn-cosmo/dh-stream-overlay",
|
"url": "https://github.com/cptn-cosmo/dh-stream-overlay",
|
||||||
"manifest": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/latest/download/module.json",
|
"manifest": "https://git.geeks.gay/cosmo/dh-stream-overlay/raw/branch/main/module.json",
|
||||||
"download": "https://github.com/cptn-cosmo/dh-stream-overlay/releases/download/1.0.0/dh-stream-overlay.zip",
|
"download": "https://git.geeks.gay/cosmo/dh-stream-overlay/releases/download/2.0.0/dh-stream-overlay.zip",
|
||||||
"description": "A stream overlay module for Daggerheart that displays chat and a linked party actor sheet."
|
"description": "A stream overlay module for Daggerheart that displays chat and a linked party actor sheet."
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +191,149 @@ Hooks.once("init", () => {
|
||||||
scope: "world", config: false, type: Boolean, default: true
|
scope: "world", config: false, type: Boolean, default: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "autoExpandChat", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.AutoExpandChat",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.AutoExpandChatHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "hideChatActions", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.HideChatActions",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.HideChatActionsHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "cardLayout", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.CardLayout",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.CardLayoutHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
"standard": "DH_STREAM_OVERLAY.Settings.Choices.Standard",
|
||||||
|
"vertical": "DH_STREAM_OVERLAY.Settings.Choices.Vertical"
|
||||||
|
},
|
||||||
|
default: "standard",
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "carouselEnabled", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.CarouselEnabled",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.CarouselEnabledHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "carouselSpeed", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.CarouselSpeed",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.CarouselSpeedHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 5,
|
||||||
|
range: { min: 2, max: 60, step: 1 },
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "carouselEffect", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.CarouselEffect",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.CarouselEffectHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
"fade": "DH_STREAM_OVERLAY.Settings.Choices.RefFade",
|
||||||
|
"slide": "DH_STREAM_OVERLAY.Settings.Choices.RefSlide",
|
||||||
|
"zoom": "DH_STREAM_OVERLAY.Settings.Choices.RefZoom"
|
||||||
|
},
|
||||||
|
default: "fade",
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("dh-stream-overlay", "carouselGMMode", {
|
||||||
|
name: "DH_STREAM_OVERLAY.Settings.CarouselGMMode",
|
||||||
|
hint: "DH_STREAM_OVERLAY.Settings.CarouselGMModeHint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
choices: {
|
||||||
|
"cycle": "DH_STREAM_OVERLAY.Settings.Choices.RefCycle",
|
||||||
|
"static": "DH_STREAM_OVERLAY.Settings.Choices.RefStatic",
|
||||||
|
"hidden": "DH_STREAM_OVERLAY.Settings.Choices.RefHidden"
|
||||||
|
},
|
||||||
|
default: "cycle",
|
||||||
|
onChange: () => { if (document.body.classList.contains("stream")) location.reload(); }
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// CAROUSEL LOGIC
|
||||||
|
// =============================================================================
|
||||||
|
let carouselIndex = 0;
|
||||||
|
let carouselTimer = null;
|
||||||
|
|
||||||
|
function startCarousel() {
|
||||||
|
if (carouselTimer) clearInterval(carouselTimer);
|
||||||
|
const speed = game.settings.get("dh-stream-overlay", "carouselSpeed") || 5;
|
||||||
|
carouselTimer = setInterval(() => {
|
||||||
|
cycleCarousel();
|
||||||
|
}, speed * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCarousel() {
|
||||||
|
if (carouselTimer) {
|
||||||
|
clearInterval(carouselTimer);
|
||||||
|
carouselTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleCarousel() {
|
||||||
|
const container = document.querySelector(".dh-party-grid.carousel-mode");
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const cards = container.querySelectorAll(".dh-party-card");
|
||||||
|
if (cards.length === 0) return;
|
||||||
|
|
||||||
|
// Increment
|
||||||
|
carouselIndex++;
|
||||||
|
if (carouselIndex >= cards.length) carouselIndex = 0;
|
||||||
|
|
||||||
|
updateCarouselClasses(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCarouselClasses(container) {
|
||||||
|
if (!container) return;
|
||||||
|
const cards = container.querySelectorAll(".dh-party-card");
|
||||||
|
|
||||||
|
// Safety: Ensure index is valid
|
||||||
|
if (carouselIndex >= cards.length) carouselIndex = 0;
|
||||||
|
|
||||||
|
cards.forEach((card, idx) => {
|
||||||
|
// Reset classes
|
||||||
|
card.classList.remove("active-slide", "exit-slide");
|
||||||
|
|
||||||
|
if (idx === carouselIndex) {
|
||||||
|
card.classList.add("active-slide");
|
||||||
|
} else {
|
||||||
|
// Optional: Mark previous slide for exit animation if needed
|
||||||
|
// For simple fade, just lacking 'active-slide' hides it
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// ... inside renderPartyOverlay function ...
|
// ... inside renderPartyOverlay function ...
|
||||||
|
|
@ -353,24 +495,45 @@ function renderPartyOverlay(container) {
|
||||||
const gms = game.users.filter(u => u.isGM);
|
const gms = game.users.filter(u => u.isGM);
|
||||||
|
|
||||||
if (players.length === 0 && gms.length === 0) {
|
if (players.length === 0 && gms.length === 0) {
|
||||||
container.innerHTML = `<div style="color:white; opacity:0.6; padding:10px;">No Active Players or GM Found</div>`;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = `<div class="dh-party-grid">`;
|
let layoutMode = "standard";
|
||||||
|
try { layoutMode = game.settings.get("dh-stream-overlay", "cardLayout"); } catch (e) { }
|
||||||
|
|
||||||
// 1. Render GM(s)
|
let carouselEnabled = false;
|
||||||
|
let carouselEffect = "fade";
|
||||||
|
let carouselGMMode = "cycle";
|
||||||
|
try {
|
||||||
|
carouselEnabled = game.settings.get("dh-stream-overlay", "carouselEnabled");
|
||||||
|
carouselEffect = game.settings.get("dh-stream-overlay", "carouselEffect");
|
||||||
|
carouselGMMode = game.settings.get("dh-stream-overlay", "carouselGMMode");
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
const renderPips = (val, max, type) => {
|
||||||
|
let h = `<div class="dh-pips-container ${type}">`;
|
||||||
|
for (let i = 1; i <= max; i++) {
|
||||||
|
h += `<div class="dh-pip ${i <= val ? 'marked' : 'open'}"></div>`;
|
||||||
|
}
|
||||||
|
h += `</div>`;
|
||||||
|
return h;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- HTML GENERATORS ---
|
||||||
|
|
||||||
|
// GM HTML Generator
|
||||||
|
let gmHtml = "";
|
||||||
gms.forEach(gm => {
|
gms.forEach(gm => {
|
||||||
const img = gm.avatar || "icons/svg/mystery-man.svg";
|
const img = gm.avatar || "icons/svg/mystery-man.svg";
|
||||||
const name = gm.name; // GM just uses their user name usually
|
const name = gm.name;
|
||||||
|
|
||||||
html += `
|
gmHtml += `
|
||||||
<div class="dh-party-card" style="min-height: 100px;">
|
<div class="dh-party-card">
|
||||||
<div class="dh-card-img-wrapper" style="width: 100px;">
|
<div class="dh-card-img-wrapper">
|
||||||
<img class="dh-card-img" src="${img}" />
|
<img class="dh-card-img" src="${img}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="dh-card-content">
|
<div class="dh-card-content">
|
||||||
<div class="dh-card-header" style="border-bottom:none; padding-bottom:0;">
|
<div class="dh-card-header no-border">
|
||||||
<div class="dh-card-name" style="color: #fca5a5;">Game Master</div>
|
<div class="dh-card-name" style="color: #fca5a5;">Game Master</div>
|
||||||
<div class="dh-card-meta" style="font-size:1.1em; color:white; margin-top:5px;">${name}</div>
|
<div class="dh-card-meta" style="font-size:1.1em; color:white; margin-top:5px;">${name}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -379,6 +542,9 @@ function renderPartyOverlay(container) {
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Player HTML Generator
|
||||||
|
let playerHtml = "";
|
||||||
|
|
||||||
// 2. Render Players
|
// 2. Render Players
|
||||||
// Check setting availability
|
// Check setting availability
|
||||||
let nameMode = "char-top";
|
let nameMode = "char-top";
|
||||||
|
|
@ -407,14 +573,7 @@ function renderPartyOverlay(container) {
|
||||||
showCommunity = game.settings.get("dh-stream-overlay", "showCommunity");
|
showCommunity = game.settings.get("dh-stream-overlay", "showCommunity");
|
||||||
} catch (e) { console.warn("DH OVERLAY: Settings not ready yet, using default"); }
|
} catch (e) { console.warn("DH OVERLAY: Settings not ready yet, using default"); }
|
||||||
|
|
||||||
const renderPips = (val, max, type) => {
|
|
||||||
let h = `<div class="dh-pips-container ${type}">`;
|
|
||||||
for (let i = 1; i <= max; i++) {
|
|
||||||
h += `<div class="dh-pip ${i <= val ? 'marked' : 'open'}"></div>`;
|
|
||||||
}
|
|
||||||
h += `</div>`;
|
|
||||||
return h;
|
|
||||||
};
|
|
||||||
|
|
||||||
players.forEach(user => {
|
players.forEach(user => {
|
||||||
const actor = user.character;
|
const actor = user.character;
|
||||||
|
|
@ -546,7 +705,7 @@ function renderPartyOverlay(container) {
|
||||||
const spotlightHtml = isSpotlightRequesting ?
|
const spotlightHtml = isSpotlightRequesting ?
|
||||||
`<div class="dh-spotlight-indicator"><i class="fas fa-hand-sparkles"></i></div>` : "";
|
`<div class="dh-spotlight-indicator"><i class="fas fa-hand-sparkles"></i></div>` : "";
|
||||||
|
|
||||||
html += `
|
playerHtml += `
|
||||||
<div class="dh-party-card ${turnClass}">
|
<div class="dh-party-card ${turnClass}">
|
||||||
${spotlightHtml}
|
${spotlightHtml}
|
||||||
<div class="dh-card-img-wrapper">
|
<div class="dh-card-img-wrapper">
|
||||||
|
|
@ -594,8 +753,45 @@ function renderPartyOverlay(container) {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
html += `</div>`;
|
// --- ASSEMBLY LOGIC ---
|
||||||
container.innerHTML = html;
|
if (carouselEnabled) {
|
||||||
|
let finalHtml = "";
|
||||||
|
let carouselClass = `carousel-mode effect-${carouselEffect}`;
|
||||||
|
|
||||||
|
// GM Logic
|
||||||
|
if (carouselGMMode === "static") {
|
||||||
|
// Static grid for GM, Carousel for Players
|
||||||
|
// Wrap in a flex container
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="dh-carousel-wrapper">
|
||||||
|
${gms.length > 0 ? `<div class="dh-party-grid layout-${layoutMode} dh-static-gm">${gmHtml}</div>` : ""}
|
||||||
|
${players.length > 0 ? `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${playerHtml}</div>` : ""}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
} else if (carouselGMMode === "hidden") {
|
||||||
|
// Only Players in Carousel
|
||||||
|
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${playerHtml}</div>`;
|
||||||
|
} else {
|
||||||
|
// "cycle" or default - Mixed
|
||||||
|
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode} ${carouselClass}">${gmHtml}${playerHtml}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Standard Mode
|
||||||
|
container.innerHTML = `<div class="dh-party-grid layout-${layoutMode}">${gmHtml}${playerHtml}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-Render: Apply Carousel State immediately (if applicable)
|
||||||
|
if (carouselEnabled) {
|
||||||
|
// Find the carousel grid (could be the main one or secondary)
|
||||||
|
const grid = container.querySelector(".dh-party-grid.carousel-mode");
|
||||||
|
if (grid) {
|
||||||
|
updateCarouselClasses(grid);
|
||||||
|
if (!carouselTimer) startCarousel();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopCarousel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
@ -612,17 +808,27 @@ Hooks.once("ready", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fallback Init Trigger
|
// Fallback Init Trigger
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
// Fallback Init Trigger
|
||||||
if (window.location.pathname.includes("/stream")) {
|
if (window.location.pathname.includes("/stream")) {
|
||||||
setTimeout(async () => {
|
const attemptInit = async (retries = 0) => {
|
||||||
if (!overlayInitialized && document.body.classList.contains("stream")) {
|
if (overlayInitialized) return;
|
||||||
console.warn("DH Stream Overlay | Fallback Init Triggered");
|
|
||||||
|
// If game is ready, or purely maximizing retries (10 seconds), force init
|
||||||
|
if (game.ready || retries > 20) {
|
||||||
|
if (!overlayInitialized) {
|
||||||
|
console.warn(`DH Stream Overlay | Fallback Init Triggered (Ready: ${game.ready}, Retries: ${retries})`);
|
||||||
overlayInitialized = true;
|
overlayInitialized = true;
|
||||||
await initStreamOverlay();
|
await initStreamOverlay();
|
||||||
}
|
}
|
||||||
}, 1000);
|
} else {
|
||||||
}
|
// Check again in 500ms
|
||||||
});
|
setTimeout(() => attemptInit(retries + 1), 500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the fallback poll
|
||||||
|
setTimeout(() => attemptInit(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
// Menu Button Hook
|
// Menu Button Hook
|
||||||
Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
||||||
|
|
@ -633,7 +839,10 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.innerHTML = `<i class="fas fa-video"></i> ${game.i18n.localize("DH_STREAM_OVERLAY.OpenStream")}`;
|
button.innerHTML = `<i class="fas fa-video"></i> ${game.i18n.localize("DH_STREAM_OVERLAY.OpenStream")}`;
|
||||||
button.style.width = "100%";
|
button.style.width = "100%";
|
||||||
button.onclick = () => window.open("/stream", "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no");
|
button.onclick = () => {
|
||||||
|
const path = window.location.pathname.replace(/\/game\/?$/, "/stream");
|
||||||
|
window.open(path, "streamOverlay", "width=1280,height=720,menubar=no,toolbar=no,status=no");
|
||||||
|
};
|
||||||
section.appendChild(button);
|
section.appendChild(button);
|
||||||
container.appendChild(section);
|
container.appendChild(section);
|
||||||
});
|
});
|
||||||
|
|
@ -645,12 +854,20 @@ Hooks.on("renderDaggerheartMenu", (_app, html) => {
|
||||||
async function initStreamOverlay() {
|
async function initStreamOverlay() {
|
||||||
console.log("DH Stream Overlay | Initializing Layout...");
|
console.log("DH Stream Overlay | Initializing Layout...");
|
||||||
|
|
||||||
|
// 0a. WAIT FOR SETTINGS
|
||||||
|
// Ensure "init" hook has run so settings are registered.
|
||||||
|
// If not, we wait a moment.
|
||||||
|
if (!game.settings.settings.has("dh-stream-overlay.autoExpandChat")) {
|
||||||
|
console.warn("DH Stream Overlay | Settings not yet registered, waiting for init...");
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
}
|
||||||
|
|
||||||
// 0. WAIT FOR GAME DATA
|
// 0. WAIT FOR GAME DATA
|
||||||
// The stream view might load faster than Foundry data.
|
// The stream view might load faster than Foundry data.
|
||||||
let dataRetries = 0;
|
let dataRetries = 0;
|
||||||
while ((!game.users || !game.users.size) && dataRetries < 20) {
|
while ((!game.users || !game.users.size) && dataRetries < 10) {
|
||||||
console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/20)`);
|
console.log(`DH Stream Overlay | Waiting for game.users... (${dataRetries}/10)`);
|
||||||
await new Promise(r => setTimeout(r, 500));
|
await new Promise(r => setTimeout(r, 200));
|
||||||
dataRetries++;
|
dataRetries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -697,10 +914,11 @@ async function initStreamOverlay() {
|
||||||
// Appending is safer for overlay z-index usually.
|
// Appending is safer for overlay z-index usually.
|
||||||
body.appendChild(wrapper);
|
body.appendChild(wrapper);
|
||||||
|
|
||||||
|
// 3. Move Chat Log
|
||||||
// 3. Move Chat Log
|
// 3. Move Chat Log
|
||||||
const retryChat = async () => {
|
const retryChat = async () => {
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
while (attempts < 50) { // Try for longer (approx 10s)
|
while (attempts < 20) { // Reduced retries to avoid long load times
|
||||||
// Try Standard DOM
|
// Try Standard DOM
|
||||||
let chatLog = document.getElementById("chat-log");
|
let chatLog = document.getElementById("chat-log");
|
||||||
|
|
||||||
|
|
@ -723,9 +941,22 @@ async function initStreamOverlay() {
|
||||||
// Force scroll to bottom
|
// Force scroll to bottom
|
||||||
chatLog.scrollTop = chatLog.scrollHeight;
|
chatLog.scrollTop = chatLog.scrollHeight;
|
||||||
console.log("DH Stream Overlay | Chat Log Moved Successfully");
|
console.log("DH Stream Overlay | Chat Log Moved Successfully");
|
||||||
|
|
||||||
|
// --- FIX: EXISTING MESSAGES ---
|
||||||
|
// Manually strip inline styles from existing messages if auto-expand is OFF
|
||||||
|
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||||
|
if (!autoExpand) {
|
||||||
|
const $log = $(chatLog);
|
||||||
|
// Remove .expanded class first
|
||||||
|
$log.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded");
|
||||||
|
|
||||||
|
// Strip inline styles
|
||||||
|
$log.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", "");
|
||||||
|
$log.find(".dice-roll .dice-tooltip").css("display", ""); // Specificity check
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await new Promise(r => setTimeout(r, 200));
|
await new Promise(r => setTimeout(r, 250));
|
||||||
attempts++;
|
attempts++;
|
||||||
}
|
}
|
||||||
console.warn("DH Stream Overlay | Chat Log NOT FOUND after retries");
|
console.warn("DH Stream Overlay | Chat Log NOT FOUND after retries");
|
||||||
|
|
@ -749,9 +980,37 @@ async function initStreamOverlay() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scroll chat to bottom on new message
|
// Scroll chat to bottom on new message
|
||||||
Hooks.on("renderChatMessageHTML", () => {
|
// Scroll chat to bottom on new message & Handle Auto-Expand Logic
|
||||||
|
Hooks.on("renderChatMessage", (message, html) => {
|
||||||
|
// Scroll
|
||||||
const log = document.getElementById("chat-log");
|
const log = document.getElementById("chat-log");
|
||||||
if (log) log.scrollTop = log.scrollHeight;
|
if (log) log.scrollTop = log.scrollHeight;
|
||||||
|
|
||||||
|
// Auto-Expand Logic (Override System Defaults if necessary)
|
||||||
|
// If Auto-Expand is DISABLED, we want to ensure tooltips don't have inline 'display: block'
|
||||||
|
// which the system might be adding by default.
|
||||||
|
try {
|
||||||
|
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||||
|
if (!autoExpand) {
|
||||||
|
// WRAPPED IN TIMEOUT to beat system race conditions
|
||||||
|
setTimeout(() => {
|
||||||
|
// 1. Remove .expanded class to force collapse state, allowing manual toggle primarily
|
||||||
|
html.find(".dice-roll.expanded, .damage-section.expanded, .target-section.expanded").removeClass("expanded");
|
||||||
|
|
||||||
|
// 2. Remove inline display styles that force expansion (failsafe)
|
||||||
|
html.find(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection").css("display", "");
|
||||||
|
|
||||||
|
// Also legacy support for non-jquery if appropriate (but standard hook passes jQuery)
|
||||||
|
if (html instanceof HTMLElement) {
|
||||||
|
html.querySelectorAll(".expanded").forEach(el => el.classList.remove("expanded"));
|
||||||
|
const nodes = html.querySelectorAll(".dice-tooltip, .dice-formula, .daggerheart-target-list, .evaluation-selection");
|
||||||
|
nodes.forEach(n => n.style.display = "");
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("DH Stream Overlay | Error in renderChatMessage hook", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 6. Combat Tracker Handling
|
// 6. Combat Tracker Handling
|
||||||
|
|
@ -817,6 +1076,8 @@ async function initStreamOverlay() {
|
||||||
|
|
||||||
function injectStyles() {
|
function injectStyles() {
|
||||||
const useGreen = game.settings.get("dh-stream-overlay", "useGreenScreen");
|
const useGreen = game.settings.get("dh-stream-overlay", "useGreenScreen");
|
||||||
|
const autoExpand = game.settings.get("dh-stream-overlay", "autoExpandChat");
|
||||||
|
const hideActions = game.settings.get("dh-stream-overlay", "hideChatActions");
|
||||||
const bgColor = useGreen ? "#00ff00" : "rgba(0, 0, 0, 0)";
|
const bgColor = useGreen ? "#00ff00" : "rgba(0, 0, 0, 0)";
|
||||||
|
|
||||||
const style = document.createElement("style");
|
const style = document.createElement("style");
|
||||||
|
|
@ -938,12 +1199,22 @@ function injectStyles() {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: transparent !important; /* Fully transparent for Chroma Key */
|
background: transparent !important; /* Fully transparent for Chroma Key */
|
||||||
/* border-right: 2px solid rgba(255, 255, 255, 0.1) !important; Optional separator */
|
/* border-right: 2px solid rgba(255, 255, 255, 0.1) !important; Optional separator */
|
||||||
pointer-events: auto;
|
pointer-events: auto !important;
|
||||||
|
z-index: 10000 !important; /* Force on top of everything */
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* GLOBAL INTERACTIVITY FORCE - ensuring chat is always clickable */
|
||||||
|
#stream-chat-container #chat-log,
|
||||||
|
#stream-chat-container .chat-message,
|
||||||
|
#stream-chat-container .message-content,
|
||||||
|
#stream-chat-container .dice-roll,
|
||||||
|
#stream-chat-container button {
|
||||||
|
pointer-events: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
#stream-right-col {
|
#stream-right-col {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
@ -1077,6 +1348,51 @@ function injectStyles() {
|
||||||
/* Hide standard timestamps to save space if needed, or style them */
|
/* Hide standard timestamps to save space if needed, or style them */
|
||||||
.message-timestamp { color: #888; font-size: 0.8em; }
|
.message-timestamp { color: #888; font-size: 0.8em; }
|
||||||
|
|
||||||
|
/* Auto-Expand Chat Logic */
|
||||||
|
${autoExpand ? `
|
||||||
|
.chat-message .message-content,
|
||||||
|
.chat-message .dice-roll,
|
||||||
|
.chat-message .dice-result,
|
||||||
|
.chat-message .dice-tooltip,
|
||||||
|
.chat-message .dice-formula {
|
||||||
|
display: block !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
height: auto !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice-tooltip { display: block !important; }
|
||||||
|
` : `
|
||||||
|
/* Force collapse default state (allow JS toggling) via HIGH SPECIFICITY */
|
||||||
|
#stream-chat-container .chat-message .dice-tooltip,
|
||||||
|
#stream-chat-container .chat-message .dice-formula,
|
||||||
|
#stream-chat-container .chat-message .daggerheart-target-list,
|
||||||
|
#stream-chat-container .chat-message .evaluation-selection,
|
||||||
|
#stream-chat-container .chat-message .dice-roll .dice-tooltip,
|
||||||
|
#stream-chat-container .chat-message .dice-result .dice-tooltip {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allow manual interaction if the .expanded class is present */
|
||||||
|
#stream-chat-container .chat-message .dice-roll.expanded .dice-tooltip,
|
||||||
|
#stream-chat-container .chat-message .dice-roll.expanded .dice-formula,
|
||||||
|
#stream-chat-container .chat-message .target-section.expanded .daggerheart-target-list,
|
||||||
|
#stream-chat-container .chat-message .damage-section.expanded .dice-formula,
|
||||||
|
#stream-chat-container .chat-message .dice-roll.expanded .dice-result .dice-tooltip {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FORCE INTERACTIVITY removed from here to be global */
|
||||||
|
`}
|
||||||
|
|
||||||
|
${hideActions ? `
|
||||||
|
.chat-message button,
|
||||||
|
.chat-message .chat-card-buttons,
|
||||||
|
.chat-message .card-buttons {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
` : ''}
|
||||||
|
|
||||||
#stream-combat-container {
|
#stream-combat-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px; right: 20px;
|
top: 20px; right: 20px;
|
||||||
|
|
@ -1091,14 +1407,16 @@ function injectStyles() {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Party Overlay Cards - GRID RESTORED */
|
/* Party Overlay Cards - GRID RESTORED */
|
||||||
.dh-party-grid {
|
.dh-party-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
|
||||||
align-items: start; /* Prevents cards from stretching vertically to match tallest */
|
align-items: start; /* Prevents cards from stretching vertically to match tallest */
|
||||||
gap: 15px;
|
gap: 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 10px;
|
padding: 20px;
|
||||||
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dh-party-card {
|
.dh-party-card {
|
||||||
|
|
@ -1260,7 +1578,120 @@ function injectStyles() {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
style.innerHTML += `
|
||||||
|
/* Vertical Layout */
|
||||||
|
.dh-party-grid.layout-vertical {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-party-grid.layout-vertical .dh-party-card {
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-party-grid.layout-vertical .dh-card-img-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 250px;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-party-grid.layout-vertical .dh-card-img {
|
||||||
|
object-position: top center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-party-grid.layout-vertical .dh-card-content {
|
||||||
|
padding: 15px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/* Carousel Mode */
|
||||||
|
style.innerHTML += `
|
||||||
|
/* Carousel Mode */
|
||||||
|
.dh-party-grid.carousel-mode {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-party-grid.carousel-mode .dh-party-card {
|
||||||
|
grid-area: 1 / 1 / -1 / -1; /* Stack on top of each other */
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
/* TRANSITION FIX: Delay visibility hide so it fades out first */
|
||||||
|
transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear 0.8s;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px; /* Constrain width in carousel */
|
||||||
|
min-height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active Slide */
|
||||||
|
.dh-party-grid.carousel-mode .dh-party-card.active-slide {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
/* Show immediately */
|
||||||
|
transition: opacity 0.8s ease-in-out, transform 0.8s ease-in-out, visibility 0s linear;
|
||||||
|
pointer-events: auto;
|
||||||
|
z-index: 10;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
|
||||||
|
/* Fade (Default behavior above) */
|
||||||
|
|
||||||
|
/* Slide */
|
||||||
|
.dh-party-grid.carousel-mode.effect-slide .dh-party-card {
|
||||||
|
transform: translateX(50px);
|
||||||
|
}
|
||||||
|
.dh-party-grid.carousel-mode.effect-slide .dh-party-card.active-slide {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zoom */
|
||||||
|
.dh-party-grid.carousel-mode.effect-zoom .dh-party-card {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
.dh-party-grid.carousel-mode.effect-zoom .dh-party-card.active-slide {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
style.innerHTML += `
|
||||||
|
/* Width Constraint for Vertical Carousel */
|
||||||
|
.dh-party-grid.carousel-mode.layout-vertical .dh-party-card {
|
||||||
|
max-width: 350px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Static GM Wrapper */
|
||||||
|
.dh-carousel-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 20px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-static-gm {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
/* Matches vertical card layout styling */
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dh-static-gm .dh-party-card {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue