Compare commits
14 Commits
1.2.3
...
adds-dice-
| Author | SHA1 | Date | |
|---|---|---|---|
| d140c785e1 | |||
|
|
18bb994a0f | ||
|
|
0e0406fa39 | ||
| 37ce450f2c | |||
| 152511b203 | |||
|
|
9e7bb02832 | ||
|
|
0a3beb2d74 | ||
| 67cffae431 | |||
| 19ff90284a | |||
|
|
6bf306dcc7 | ||
|
|
a215647947 | ||
| 741276e477 | |||
| 4da0d7b1f0 | |||
|
|
2ca9ad6f5a |
@@ -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
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -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.3</version>
|
<version>1.2.7-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<compiler-plugin.version>3.13.0</compiler-plugin.version>
|
<compiler-plugin.version>3.13.0</compiler-plugin.version>
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
if (data.results == null) {
|
||||||
|
var numberOfDice = 0
|
||||||
data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>().forEach { command ->
|
data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>().forEach { command ->
|
||||||
val dice = command.split("d")
|
val dice = command.split("d")
|
||||||
val result = IntArray(dice[0].toInt())
|
var amount = dice[0].toInt()
|
||||||
|
val limit = diceLimit.orElse(LIMIT)
|
||||||
|
if (limit < numberOfDice + amount) {
|
||||||
|
amount = limit - numberOfDice
|
||||||
|
}
|
||||||
|
numberOfDice += amount
|
||||||
|
if (amount > 0) {
|
||||||
|
val result = IntArray(amount)
|
||||||
val sides = dice[1].split("+", "-")
|
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
|
val modifier = if (dice[1].contains("+")) sides[1].toInt() else if (dice[1].contains("+")) -1 * sides[1].toInt() else 0
|
||||||
repeat(dice[0].toInt()) { index ->
|
repeat(amount) { index ->
|
||||||
result[index] = (Math.random() * sides[0].toInt() + 1).toInt()
|
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()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -89,7 +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('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>
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="dice-box"></div>
|
<div id="dice-box"></div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
import DiceBox from "/vendor/dice-box/dice-box-threejs.es.js";
|
||||||
|
|
||||||
function url() {
|
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 : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
import DiceBox from "/vendor/dice-box/dice-box-threejs.es.js";
|
|
||||||
|
|
||||||
const evtSource = new EventSource(url() + "/dice/{diceid??}/stream");
|
const evtSource = new EventSource(url() + "/dice/{diceid??}/stream");
|
||||||
|
|
||||||
const diceBox = new DiceBox("#dice-box", {
|
const diceBox = new DiceBox("#dice-box", {
|
||||||
assetPath: "/vendor/dice-box/",
|
assetPath: "/vendor/dice-box/",
|
||||||
light_intensity: 2,
|
light_intensity: 2,
|
||||||
@@ -23,26 +24,67 @@
|
|||||||
baseScale: {scale} * 10,
|
baseScale: {scale} * 10,
|
||||||
strength: Math.floor(Math.random() * 4),
|
strength: Math.floor(Math.random() * 4),
|
||||||
});
|
});
|
||||||
|
|
||||||
diceBox.initialize();
|
diceBox.initialize();
|
||||||
let timeout = 0;
|
|
||||||
evtSource.addEventListener("message", function (event) {
|
let rollQueue = [];
|
||||||
clearInterval(timeout);
|
let rolling = false;
|
||||||
let data = JSON.parse(event.data);
|
let clearTimeoutId = null;
|
||||||
diceBox.onRollComplete = () => {
|
let lastRollTime = Date.now();
|
||||||
if ({clearAfter} > 0) {
|
|
||||||
timeout = setTimeout(() => diceBox.clearDice(), {clearAfter} * 1000)
|
function scheduleClear() {
|
||||||
|
if (clearTimeoutId) clearTimeout(clearTimeoutId);
|
||||||
|
|
||||||
|
clearTimeoutId = setTimeout(() => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastRollTime >= ({clearAfter} * 1000)) {
|
||||||
|
diceBox.clearDice();
|
||||||
|
rolling = false;
|
||||||
|
processQueue();
|
||||||
|
} else {
|
||||||
|
scheduleClear(); // try again later
|
||||||
}
|
}
|
||||||
|
}, {clearAfter} * 1000);
|
||||||
}
|
}
|
||||||
diceBox.updateConfig({
|
|
||||||
|
async function processQueue() {
|
||||||
|
if (rolling || rollQueue.length === 0) return;
|
||||||
|
|
||||||
|
rolling = true;
|
||||||
|
|
||||||
|
const data = rollQueue.shift();
|
||||||
|
const rollString = data.roll
|
||||||
|
.filter(it => it.split('@')[0].split('d')[1] !== "2")
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
if (!rollString) {
|
||||||
|
rolling = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRollTime = Date.now();
|
||||||
|
|
||||||
|
await diceBox.updateConfig({
|
||||||
theme_customColorset: {
|
theme_customColorset: {
|
||||||
texture: data.theme,
|
texture: data.theme,
|
||||||
background: data.faceColor,
|
background: data.faceColor,
|
||||||
foreground: data.numberColor
|
foreground: data.numberColor
|
||||||
}
|
}
|
||||||
}).then(() => {
|
});
|
||||||
diceBox.roll(data.roll.join('&'));
|
|
||||||
})
|
await diceBox.add(rollString);
|
||||||
})
|
scheduleClear();
|
||||||
|
|
||||||
|
rolling = false;
|
||||||
|
void processQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
evtSource.addEventListener("message", function (event) {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
rollQueue.push(data);
|
||||||
|
processQueue();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -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 = ''
|
||||||
|
|||||||
Reference in New Issue
Block a user