28 Commits
1.2.0 ... main

Author SHA1 Message Date
gitea
8942988228 [no ci] prepare new Version 2025-12-10 20:40:45 +00:00
gitea
6fbedd92c7 [no ci] release 1.2.7 2025-12-10 20:40:35 +00:00
f4ec070f8f Merge pull request 'adds support for new "allAllowed" and "showResults" options in chat overlay' (#42) from let-followers-roll-on-chatoverlay into main
All checks were successful
CI / deploy (push) Successful in 5m15s
Reviewed-on: #42
2025-12-10 21:35:30 +01:00
b41d423837 adds support for new "allAllowed" and "showResults" options in chat overlay
All checks were successful
CI / deploy (push) Successful in 8m8s
CI / deploy (pull_request) Successful in 7m24s
2025-12-10 21:15:59 +01:00
gitea
18bb994a0f [no ci] prepare new Version 2025-03-01 11:21:52 +00:00
gitea
0e0406fa39 [no ci] release 1.2.6 2025-03-01 11:21:43 +00:00
37ce450f2c Merge pull request 'adds the ability to roll results externally' (#41) from allows-external-results into main
All checks were successful
CI / deploy (push) Successful in 6m3s
Reviewed-on: #41
2025-03-01 12:15:47 +01:00
152511b203 adds the ability to roll results externally
All checks were successful
CI / deploy (push) Successful in 7m26s
CI / deploy (pull_request) Successful in 5m50s
2025-03-01 11:44:09 +01:00
gitea
9e7bb02832 [no ci] prepare new Version 2025-02-22 13:56:35 +00:00
gitea
0a3beb2d74 [no ci] release 1.2.5 2025-02-22 13:56:29 +00:00
67cffae431 Merge pull request 'removes d2 from quickroll and overlay' (#40) from remove-d2 into main
All checks were successful
CI / deploy (push) Successful in 5m48s
Reviewed-on: #40
2025-02-22 14:50:48 +01:00
19ff90284a removes d2 from quickroll and overlay
All checks were successful
CI / deploy (push) Successful in 5m40s
CI / deploy (pull_request) Successful in 5m32s
2025-02-22 14:37:39 +01:00
gitea
6bf306dcc7 [no ci] prepare new Version 2025-02-22 13:15:24 +00:00
gitea
a215647947 [no ci] release 1.2.4 2025-02-22 13:15:16 +00:00
741276e477 Merge pull request 'caps the number of allowed dice' (#39) from limit-number-of-diece into main
All checks were successful
CI / deploy (push) Successful in 6m6s
Reviewed-on: #39
2025-02-22 14:09:17 +01:00
4da0d7b1f0 caps the number of allowed dice
All checks were successful
CI / deploy (push) Successful in 5m39s
CI / deploy (pull_request) Successful in 5m38s
2025-02-22 13:53:49 +01:00
gitea
2ca9ad6f5a [no ci] prepare new Version 2025-02-22 11:44:33 +00:00
gitea
ab51fdc281 [no ci] release 1.2.3 2025-02-22 11:44:25 +00:00
c04b4f67d2 Merge pull request 'reregister users on roll' (#38) from register-user-on-roll into main
All checks were successful
CI / deploy (push) Successful in 6m6s
Reviewed-on: #38
2025-02-22 12:38:27 +01:00
d030eda0e9 reregister users on roll
All checks were successful
CI / deploy (push) Successful in 5m48s
CI / deploy (pull_request) Successful in 6m0s
2025-02-22 12:16:22 +01:00
gitea
8a4032756e [no ci] prepare new Version 2025-02-16 19:05:25 +00:00
gitea
717d337535 [no ci] release 1.2.2 2025-02-16 19:05:17 +00:00
c79cce8651 Merge pull request 'adds install button' (#35) from install-as-app into main
All checks were successful
CI / deploy (push) Successful in 5m42s
Reviewed-on: #35
2025-02-16 19:59:43 +01:00
fc82d0397f adds install button
All checks were successful
CI / deploy (push) Successful in 5m42s
CI / deploy (pull_request) Successful in 5m37s
2025-02-16 19:45:54 +01:00
gitea
cd9942068f [no ci] prepare new Version 2025-02-16 17:52:56 +00:00
gitea
fb4c0e7b92 [no ci] release 1.2.1 2025-02-16 17:52:49 +00:00
1a0528933b adds d2 to easy roll
All checks were successful
CI / deploy (push) Successful in 5m49s
2025-02-16 18:47:02 +01:00
gitea
0d324239bf [no ci] prepare new Version 2025-02-16 17:37:27 +00:00
18 changed files with 162 additions and 73 deletions

View File

@@ -42,6 +42,8 @@ services:
restart: always restart: always
ports: ports:
- "8080:8080" - "8080:8080"
environment:
DICE_LIMIT: 30 # OPTIONAL: amount of dice allowed to roll (default: 30)
``` ```
Run the container with: Run the container with:
```bash ```bash

View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.arindy</groupId> <groupId>de.arindy</groupId>
<artifactId>dice-tower</artifactId> <artifactId>dice-tower</artifactId>
<version>1.2.0</version> <version>1.2.8-SNAPSHOT</version>
<properties> <properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version> <compiler-plugin.version>3.13.0</compiler-plugin.version>

View File

@@ -21,14 +21,16 @@ class ChatOverlayResource {
@QueryParam("modsAllowed") modsAllowed: Boolean = false, @QueryParam("modsAllowed") modsAllowed: Boolean = false,
@QueryParam("vipAllowed") vipAllowed: Boolean = false, @QueryParam("vipAllowed") vipAllowed: Boolean = false,
@QueryParam("subsAllowed") subsAllowed: Boolean = false, @QueryParam("subsAllowed") subsAllowed: Boolean = false,
@QueryParam("allAllowed") allAllowed: Boolean = false,
@QueryParam("cmd") cmd: String? = "roll", @QueryParam("cmd") cmd: String? = "roll",
@QueryParam("theme") theme: String? = "default", @QueryParam("theme") theme: String? = "default",
@QueryParam("faceColor") faceColor: String? = "#ff0202", @QueryParam("faceColor") faceColor: String? = "#ff0202",
@QueryParam("numberColor") numberColor: String? = "#ffffff", @QueryParam("numberColor") numberColor: String? = "#ffffff",
@QueryParam("clearAfter") clearAfter: Long? = -1, @QueryParam("clearAfter") clearAfter: Long? = -1,
@QueryParam("timeout") timeout: Long? = -1 @QueryParam("timeout") timeout: Long? = -1,
@QueryParam("showResults") showResults: Boolean = true
): TemplateInstance { ): TemplateInstance {
return Templates.chatoverlay(channel, scale ?: 7, maxDice ?: 20, modsAllowed, vipAllowed, subsAllowed, cmd ?: "roll", theme ?: "default", faceColor ?: "#ff0202", numberColor ?: "#ffffff", clearAfter ?: 10, timeout ?: 60) return Templates.chatoverlay(channel, scale ?: 7, maxDice ?: 20, modsAllowed, vipAllowed, subsAllowed, allAllowed, cmd ?: "roll", theme ?: "default", faceColor ?: "#ff0202", numberColor ?: "#ffffff", clearAfter ?: 10, timeout ?: 60, showResults)
} }
@GET @GET

View File

@@ -14,12 +14,19 @@ import jakarta.ws.rs.sse.OutboundSseEvent
import jakarta.ws.rs.sse.Sse import jakarta.ws.rs.sse.Sse
import jakarta.ws.rs.sse.SseBroadcaster import jakarta.ws.rs.sse.SseBroadcaster
import jakarta.ws.rs.sse.SseEventSink import jakarta.ws.rs.sse.SseEventSink
import org.eclipse.microprofile.config.inject.ConfigProperty
import java.util.Optional
import java.util.UUID import java.util.UUID
private const val LIMIT = 30
@Path("/dice/{id}") @Path("/dice/{id}")
@ApplicationScoped @ApplicationScoped
final class DiceResource(@Context val sse: Sse) { final class DiceResource(@Context val sse: Sse) {
@ConfigProperty(name = "dice.limit")
private lateinit var diceLimit: Optional<Int>
private var eventBuilder: OutboundSseEvent.Builder = sse.newEventBuilder() private var eventBuilder: OutboundSseEvent.Builder = sse.newEventBuilder()
private var sseBroadcasters: MutableMap<String, SseBroadcaster> = HashMap() private var sseBroadcasters: MutableMap<String, SseBroadcaster> = HashMap()
@@ -28,18 +35,31 @@ final class DiceResource(@Context val sse: Sse) {
fun parseCommand(@PathParam("id") id: String, data: RollPayload) { fun parseCommand(@PathParam("id") id: String, data: RollPayload) {
data.room = id.split(":")[0] data.room = id.split(":")[0]
data.user = id.split(":")[1] data.user = id.split(":")[1]
val results = ArrayList<Results>() val results = ArrayList<Results>()
data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>().forEach { command -> if (data.results == null) {
val dice = command.split("d") var numberOfDice = 0
val result = IntArray(dice[0].toInt()) data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>().forEach { command ->
val sides = dice[1].split("+", "-") val dice = command.split("d")
val modifier = if (dice[1].contains("+")) sides[1].toInt() else if (dice[1].contains("+")) -1 * sides[1].toInt() else 0 var amount = dice[0].toInt()
repeat(dice[0].toInt()) { index -> val limit = diceLimit.orElse(LIMIT)
result[index] = (Math.random() * sides[0].toInt() + 1).toInt() if (limit < numberOfDice + amount) {
amount = limit - numberOfDice
}
numberOfDice += amount
if (amount > 0) {
val result = IntArray(amount)
val sides = dice[1].split("+", "-")
val modifier = if (dice[1].contains("+")) sides[1].toInt() else if (dice[1].contains("+")) -1 * sides[1].toInt() else 0
repeat(amount) { index ->
result[index] = (Math.random() * sides[0].toInt() + 1).toInt()
}
results.add(Results(sides[0].toInt(), modifier, result.sum() + modifier, result.map { Roll(it) }.toTypedArray()))
}
} }
results.add(Results(sides[0].toInt(), modifier, result.sum() + modifier, result.map { Roll(it) }.toTypedArray())) } else {
results.addAll(data.results)
} }
val map = results.map { r -> val map = results.map { r ->
"${r.rolls.size}d${r.sides}@${ "${r.rolls.size}d${r.sides}@${
if (r.sides == 100) r.rolls.map { roll -> Roll(roll.value / 10 * 10) } if (r.sides == 100) r.rolls.map { roll -> Roll(roll.value / 10 * 10) }
@@ -48,13 +68,13 @@ final class DiceResource(@Context val sse: Sse) {
}.toTypedArray() }.toTypedArray()
data.roll = map + results.filter { it.sides == 100 }.map { r -> "${r.rolls.size}d10@${r.rolls.map { roll -> Roll(roll.value % 10) }.joinToString(",")}"}.toTypedArray() data.roll = map + results.filter { it.sides == 100 }.map { r -> "${r.rolls.size}d10@${r.rolls.map { roll -> Roll(roll.value % 10) }.joinToString(",")}"}.toTypedArray()
if (data.roll.all { it.trim().isNotEmpty() }) {
results(data.room!!, Result(data.name, data.user!!, data.faceColor, null))
}
sseBroadcasters[id]?.broadcast( sseBroadcasters[id]?.broadcast(
eventBuilder.id((UUID.randomUUID()).toString()) eventBuilder.id((UUID.randomUUID()).toString())
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build() .mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build()
) )
if (data.roll.all { it.trim().isNotEmpty() }) {
results(data.room!!, Result(data.name, data.user!!, data.faceColor, null))
}
Thread.sleep(1000) Thread.sleep(1000)
results(data.room!!, Result(data.name, data.user!!, data.faceColor, results.toTypedArray())) results(data.room!!, Result(data.name, data.user!!, data.faceColor, results.toTypedArray()))
} }

View File

@@ -20,7 +20,7 @@ class OverlayResource {
@GET @GET
@Path("/results") @Path("/results")
@Produces(MediaType.TEXT_HTML) @Produces(MediaType.TEXT_HTML)
fun results(@PathParam("diceid") room: String, @QueryParam("name") name: String?, @QueryParam("user") user: String?): TemplateInstance { fun results(@PathParam("diceid") room: String, @QueryParam("name") name: String?): TemplateInstance {
return Templates.results(room, name ?: "all", user ?: "all") return Templates.results(room, name ?: "all")
} }
} }

View File

@@ -6,7 +6,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection
data class RollPayload( data class RollPayload(
val name: String, val name: String,
var command: String, var command: String,
var predetermined: Boolean? = false, val results: Array<DiceResource.Results>?,
val faceColor: String = "white", val faceColor: String = "white",
val numberColor: String = "white", val numberColor: String = "white",
val theme: String = "default", val theme: String = "default",

View File

@@ -9,7 +9,7 @@ object Templates {
external fun overlay(diceid: String, scale: Int?, clearAfter: Long?): TemplateInstance external fun overlay(diceid: String, scale: Int?, clearAfter: Long?): TemplateInstance
@JvmStatic @JvmStatic
external fun results(room: String, name: String?, user: String?): TemplateInstance external fun results(room: String, name: String?): TemplateInstance
@JvmStatic @JvmStatic
external fun index(version: String): TemplateInstance external fun index(version: String): TemplateInstance
@@ -25,11 +25,13 @@ object Templates {
modsAllowed: Boolean, modsAllowed: Boolean,
vipAllowed: Boolean, vipAllowed: Boolean,
subsAllowed: Boolean, subsAllowed: Boolean,
allAllowed: Boolean,
cmd: String?, cmd: String?,
theme: String?, theme: String?,
faceColor: String?, faceColor: String?,
numberColor: String?, numberColor: String?,
clearAfter: Long?, clearAfter: Long?,
timeout: Long? timeout: Long?,
showResults: Boolean?
): TemplateInstance ): TemplateInstance
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -14,6 +14,17 @@ function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : ''); return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
} }
function register() {
let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + '/register')
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
overlay: document.getElementById('overlayId').value,
id: document.getElementById('room').value + ':' + localStorage.getItem('userId')
}))
}
function start(event = undefined) { function start(event = undefined) {
if ((!event || event.keyCode === 13) && document.getElementById('name').value.length > 0 && document.getElementById('room').value.length > 0) { if ((!event || event.keyCode === 13) && document.getElementById('name').value.length > 0 && document.getElementById('room').value.length > 0) {
document.getElementById('overlayId').value = url() + '/overlay/' + document.getElementById('room').value + ':' + localStorage.getItem('userId') + '?scale=10&clearAfter=30'; document.getElementById('overlayId').value = url() + '/overlay/' + document.getElementById('room').value + ':' + localStorage.getItem('userId') + '?scale=10&clearAfter=30';
@@ -61,14 +72,7 @@ function start(event = undefined) {
localStorage.setItem(document.getElementById('name').value + '-started', "true") localStorage.setItem(document.getElementById('name').value + '-started', "true")
let httpRequest = new XMLHttpRequest(); register();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + '/register')
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
overlay: document.getElementById('overlayId').value,
id: document.getElementById('room').value + ':' + localStorage.getItem('userId')
}))
if (document.getElementById('gm').checked) { if (document.getElementById('gm').checked) {
document.getElementById('resultSwitch').checked = true; document.getElementById('resultSwitch').checked = true;
document.getElementById('resultFrame').src = document.getElementById('resultsId').value; document.getElementById('resultFrame').src = document.getElementById('resultsId').value;
@@ -98,18 +102,21 @@ function start(event = undefined) {
newOverlay.appendChild(newInput); newOverlay.appendChild(newInput);
overlays.appendChild(newOverlay); overlays.appendChild(newOverlay);
let dice = document.createElement('iframe'); if (!document.getElementById(data.id + '-diceFrame')) {
dice.id = data.id + '-diceFrame' let dice = document.createElement('iframe');
dice.style.width = "50%"; dice.id = data.id + '-diceFrame'
dice.style.height = "100%"; dice.style.width = "50%";
dice.style.overflow = "hidden"; dice.style.height = "100%";
dice.style.border = "0"; dice.style.overflow = "hidden";
dice.style.zIndex = "4"; dice.style.border = "0";
dice.style.position = "absolute"; dice.style.zIndex = "4";
dice.style.top = "0"; dice.style.position = "absolute";
dice.style.left = "50%"; dice.style.top = "0";
dice.src = data.overlay; dice.style.left = "50%";
document.getElementById('results-dice').appendChild(dice) dice.src = data.overlay;
document.getElementById('results-dice').appendChild(dice)
}
} }
configurePopover(); configurePopover();
@@ -125,6 +132,7 @@ function rollEasy(dice) {
function roll(event) { function roll(event) {
if ((!event || event.keyCode === 13) && document.getElementById('command').value?.length > 0) { if ((!event || event.keyCode === 13) && document.getElementById('command').value?.length > 0) {
register()
let httpRequest = new XMLHttpRequest(); let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + ':' + localStorage.getItem(`userId`)) httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + ':' + localStorage.getItem(`userId`))
httpRequest.setRequestHeader('Content-Type', 'application/json') httpRequest.setRequestHeader('Content-Type', 'application/json')
@@ -207,11 +215,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}) })
document.getElementById('resultDiceSwitch').addEventListener('change', function () { document.getElementById('resultDiceSwitch').addEventListener('change', function () {
if (!this.checked) { document.getElementById('results-dice').hidden = !this.checked;
document.getElementById('results-dice').hidden = true
} else {
document.getElementById('results-dice').hidden = false
}
}) })
document.getElementById('chatOverlayLink').href = url() + '/chatoverlay' document.getElementById('chatOverlayLink').href = url() + '/chatoverlay'

View File

@@ -0,0 +1,18 @@
{
"name": "Dice-Tower",
"icons": [
{
"src": "192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display_override": ["window-controls-overlay", "minimal-ui"],
"display": "standalone"
}

View File

@@ -32,6 +32,22 @@ button:active {
background: #222222; background: #222222;
} }
#install {
padding: 10px;
border: #666666 3px solid;
border-radius: 10px;
background: #666666;
color: #fff
}
#install:hover {
background: #444444;
}
#install:active {
background: #222222;
}
input { input {
margin: 10px; margin: 10px;
} }

View File

@@ -123,15 +123,15 @@ class ColorPicker extends HTMLElement {
<div class="bg-cover" id="controls"> <div class="bg-cover" id="controls">
<label for="hue"> <label for="hue">
<abbr title="Hue: The color tint.">H</abbr> <abbr title="Hue: The color tint.">H</abbr>
<input type="range" id="hue" min="0" max="360" value="0"> <input type="range" id="hue" min="0" max="360" value="0" step="0.01">
</label> </label>
<label for="saturation"> <label for="saturation">
<abbr title="Saturation: The amount of gray in the color.">S</abbr> <abbr title="Saturation: The amount of gray in the color.">S</abbr>
<input type="range" id="saturation" min="0" max="100" value="0"> <input type="range" id="saturation" min="0" max="100" value="0" step="0.01">
</label> </label>
<label for="lightness"> <label for="lightness">
<abbr title="Lightness: How light or dark the color is.">L</abbr> <abbr title="Lightness: How light or dark the color is.">L</abbr>
<input type="range" id="lightness" min="0" max="100" value="0"> <input type="range" id="lightness" min="0" max="100" value="0" step="0.01">
</label> </label>
</div> </div>
</div>`; </div>`;
@@ -251,9 +251,9 @@ class ColorPicker extends HTMLElement {
*/ */
updateColor() { updateColor() {
const hue = Math.round( this.hueInput.value ); const hue = parseFloat( this.hueInput.value );
const saturation = Math.round( this.saturationInput.value ); const saturation = parseFloat( this.saturationInput.value );
const lightness = Math.round( this.lightnessInput.value ); const lightness = parseFloat( this.lightnessInput.value );
const hsla = [ hue, saturation, lightness ]; const hsla = [ hue, saturation, lightness ];
const rgba = this.HSLAToRGBA( hsla ); const rgba = this.HSLAToRGBA( hsla );
@@ -324,6 +324,10 @@ class ColorPicker extends HTMLElement {
*/ */
setColor( color = 'hsla(60, 100%, 50%, 0.5)' ) { setColor( color = 'hsla(60, 100%, 50%, 0.5)' ) {
if ( /^[0-9A-F]{3,6}$/i.test( color ) ) {
color = '#' + color;
}
const rgba = this.colorToRGBA( color ); const rgba = this.colorToRGBA( color );
const hsla = this.rgbToHSLA( rgba ); const hsla = this.rgbToHSLA( rgba );
@@ -484,13 +488,15 @@ class ColorPicker extends HTMLElement {
} }
// Calculate hue // Calculate hue
let hue; let hue = 0;
if ( max === normalizedR ) { if ( max !== min ) {
hue = ( ( normalizedG - normalizedB ) / ( max - min ) ) % 6; if ( max === normalizedR ) {
} else if ( max === normalizedG ) { hue = ( ( normalizedG - normalizedB ) / ( max - min ) ) % 6;
hue = ( ( normalizedB - normalizedR ) / ( max - min ) + 2 ); } else if ( max === normalizedG ) {
} else { hue = ( ( normalizedB - normalizedR ) / ( max - min ) + 2 );
hue = ( ( normalizedR - normalizedG ) / ( max - min ) + 4 ); } else {
hue = ( ( normalizedR - normalizedG ) / ( max - min ) + 4 );
}
} }
// Convert hue to degrees // Convert hue to degrees
@@ -500,7 +506,7 @@ class ColorPicker extends HTMLElement {
hue += 360; hue += 360;
} }
return [ Math.round( hue ), Math.round( saturation * 100 ), Math.round( lightness * 100 ), a ]; return [ parseFloat(hue.toFixed(2)), parseFloat((saturation * 100).toFixed(2)), parseFloat((lightness * 100).toFixed(2)), a ];
} }

View File

@@ -31,9 +31,7 @@
//maxDice //maxDice
ComfyJS.onCommand = async (user, command, message, flags) => { ComfyJS.onCommand = async (user, command, message, flags) => {
if (( if ((flags.broadcaster || {allAllowed} || ({modsAllowed} && flags.mod) || ({vipAllowed} && flags.vip) || ({subsAllowed} && flags.subscriber)) && '{cmd}' === command && !shouldWait() && message.match(/^\d+d(4|6|8|10|12|20|100)$/) && (+message.split('d')[0] <= {maxDice})
flags.broadcaster || ({modsAllowed} && flags.mod) || ({vipAllowed} && flags.vip) || ({subsAllowed} && flags.subscriber
)) && '{cmd}' === command && !shouldWait() && message.match(/^\d+d(4|6|8|10|12|20|100)$/) && (+message.split('d')[0] <= {maxDice})
) { ) {
toggleWait(true); toggleWait(true);
@@ -45,7 +43,9 @@
}) })
document.getElementById('results').innerHTML = '<strong>' + user + '</strong> rolls <strong>' + message + '</strong>:<br/> [' + values.map(value => value === 1 ? '<strong style="text-shadow: 2px 2px 10px red">' + value + '</strong>' : value === result.sides ? '<strong style="text-shadow: 2px 2px 10px green">' + value + '</strong>' : value).join(' + ') + '] = <strong>' + result.total + '</strong> ' document.getElementById('results').innerHTML = '<strong>' + user + '</strong> rolls <strong>' + message + '</strong>:<br/> [' + values.map(value => value === 1 ? '<strong style="text-shadow: 2px 2px 10px red">' + value + '</strong>' : value === result.sides ? '<strong style="text-shadow: 2px 2px 10px green">' + value + '</strong>' : value).join(' + ') + '] = <strong>' + result.total + '</strong> '
}) })
document.getElementById('results').showPopover() if({showResults}) {
document.getElementById('results').showPopover()
}
setTimeout(() => { setTimeout(() => {
diceBox.clearDice(); diceBox.clearDice();
document.getElementById('results').hidePopover() document.getElementById('results').hidePopover()

View File

@@ -52,6 +52,10 @@
<input type="checkbox" id="subsAllowed"> <input type="checkbox" id="subsAllowed">
<span class="checkmark"></span> <span class="checkmark"></span>
</label> </label>
<label class="checkbox" id="allAllowed-container">Allow everyone to roll (yes, everyone!!)
<input type="checkbox" id="allAllowed">
<span class="checkmark"></span>
</label>
</div> </div>
<div> <div>
<label for="scale">Dice-Scale </label> <label for="scale">Dice-Scale </label>
@@ -64,6 +68,10 @@
<input type="number" id="clearAfter" style="width: 50px; margin-top: 20px" value="10"/> <input type="number" id="clearAfter" style="width: 50px; margin-top: 20px" value="10"/>
<label for="timeout">Command-timeout (in seconds)</label> <label for="timeout">Command-timeout (in seconds)</label>
<input type="number" id="timeout" style="width: 50px; margin-top: 20px" value="60"/> <input type="number" id="timeout" style="width: 50px; margin-top: 20px" value="60"/>
<label class="checkbox" id="showResults-container">Show Results Overlay
<input type="checkbox" id="showResults" checked>
<span class="checkmark"></span>
</label>
</div> </div>
<div id="dice-box" style="width: 850px; height: 400px"> <div id="dice-box" style="width: 850px; height: 400px">
@@ -244,7 +252,9 @@
"&timeout=" + document.getElementById('timeout').value + "&timeout=" + document.getElementById('timeout').value +
"&modsAllowed=" + document.getElementById('modsAllowed').checked + "&modsAllowed=" + document.getElementById('modsAllowed').checked +
"&vipAllowed=" + document.getElementById('vipAllowed').checked + "&vipAllowed=" + document.getElementById('vipAllowed').checked +
"&subsAllowed=" + document.getElementById('subsAllowed').checked "&subsAllowed=" + document.getElementById('subsAllowed').checked +
"&allAllowed=" + document.getElementById('allAllowed').checked +
"&showResults=" + document.getElementById('showResults').checked
} }
}) })
</script> </script>

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Dice-Tower</title> <title>Dice-Tower</title>
<meta name="version" content="{version}"> <meta name="version" content="{version}">
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="/vendor/w3css/4/w3.css"> <link rel="stylesheet" href="/vendor/w3css/4/w3.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/fontawesome.css"> <link rel="stylesheet" href="/vendor/font-awesome/css/fontawesome.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/all.css"> <link rel="stylesheet" href="/vendor/font-awesome/css/all.css">
@@ -33,6 +33,17 @@
if (!localStorage.getItem("userId")) { if (!localStorage.getItem("userId")) {
localStorage.setItem("userId", self.crypto.randomUUID()); localStorage.setItem("userId", self.crypto.randomUUID());
} }
addEventListener("beforeinstallprompt", (event) => {
event.preventDefault()
let install = document.createElement('button');
install.id = 'install'
install.style.position = 'absolute'
install.style.top = '25px'
install.style.right = '25px'
install.innerHTML = 'Install Dice-Tower'
install.onclick = () => event.prompt()
document.body.appendChild(install)
});
</script> </script>
<div class="w3-container w3-content" <div class="w3-container w3-content"
style="height: 95vh; display: flex; flex-direction: column; justify-content: space-between; padding: 25px"> style="height: 95vh; display: flex; flex-direction: column; justify-content: space-between; padding: 25px">
@@ -78,6 +89,7 @@
<button style="border: transparent; border-radius: 100%; font-size: large; font-weight: bold; height: 50px; width: 50px" <button style="border: transparent; border-radius: 100%; font-size: large; font-weight: bold; height: 50px; width: 50px"
onclick="addDice()">+ onclick="addDice()">+
</button> </button>
<!--<button style="font-size: large; font-weight: bold;" onclick="rollEasy('d2')">D2</button><-->
<button style="font-size: large; font-weight: bold;" onclick="rollEasy('d4')">D4</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d4')">D4</button>
<button style="font-size: large; font-weight: bold;" onclick="rollEasy('d6')">D6</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d6')">D6</button>
<button style="font-size: large; font-weight: bold;" onclick="rollEasy('d8')">D8</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d8')">D8</button>
@@ -190,11 +202,8 @@
<h2 style="text-align: center">How-To</h2> <h2 style="text-align: center">How-To</h2>
<ul> <ul>
<li> <li>
Join a room by entering your character name and the name of the room.<br/> Join a room by entering your character name and the name of the room
<strong>If you are a GM, make sure to join the room first or let all other players rejoin to get all
Overlay-URLs.</strong>
</li> </li>
<li>Open your Dice-Overlay either in a new Tab or as a browser source in OBS</li> <li>Open your Dice-Overlay either in a new Tab or as a browser source in OBS</li>
<ul> <ul>
<li>You can configure your Overlay with query parameters (for more information hover over the link) <li>You can configure your Overlay with query parameters (for more information hover over the link)

View File

@@ -40,7 +40,7 @@
foreground: data.numberColor foreground: data.numberColor
} }
}).then(() => { }).then(() => {
diceBox.roll(data.roll.join('&')); diceBox.roll(data.roll.filter(it => it.split('@')[0].split('d')[1] !== "2").join('&'));
}) })
}) })
</script> </script>

View File

@@ -17,9 +17,9 @@
const evtSource = new EventSource(url() + '/dice/{room}/results'); const evtSource = new EventSource(url() + '/dice/{room}/results');
evtSource.addEventListener('message', function (event) { evtSource.addEventListener('message', function (event) {
let data = JSON.parse(event.data); let data = JSON.parse(event.data);
if ("{name}" === "all" && "{user}" === "all" || "{name}" === data.name && "{user}" === data.user || "{name}" === "all" && "{user}" === data.user || "{name}" === data.name && "{user}" === "all") { if ("{name}" === "all" || "{name}" === data.name) {
let name = document.getElementById(data.user + '-' + data.name) ?? document.createElement('div'); let name = document.getElementById(data.name) ?? document.createElement('div');
name.id = data.user + '-' + data.name; name.id = data.name;
name.replaceChildren(...[]); name.replaceChildren(...[]);
let node = document.createElement('p'); let node = document.createElement('p');
let resultText = '' let resultText = ''